version 1.8, 2002/03/16 20:29:21 |
version 1.9, 2002/03/18 01:45:55 |
|
|
* Global variables |
* Global variables |
*/ |
*/ |
/* |
/* |
* undo_disable_flag - |
* undo_disable_flag: Stop doing undo (useful when we know are |
* |
* going to deal with huge deletion/insertions |
* Stop doing undo (useful when we know are |
* that we don't plan to undo) |
* going to deal with huge deletion/insertions |
|
* that we don't plan to undo) |
|
*/ |
*/ |
int undo_disable_flag; |
int undo_disable_flag; |
int undoaction; /* Are we called indirectly from undo()? */ |
|
|
|
/* |
/* |
* Local functions |
* Local functions |
*/ |
*/ |
static int find_offset(LINE *, int); |
static int find_absolute_dot(LINE *, int); |
static int find_linep(int, LINE **, int *); |
static int find_line_offset(int, LINE **, int *); |
static struct undo_rec *new_undo_record(void); |
static struct undo_rec *new_undo_record(void); |
static int drop_oldest_undo_record(void); |
static int drop_oldest_undo_record(void); |
|
|
|
/* |
|
* find_{absolute_dot,line_offset}() |
|
* |
|
* Find an absolute dot in the buffer from a line/offset pair, and vice-versa. |
|
* |
|
* Since lines can be deleted while they are referenced by undo record, we |
|
* need to have an absolute dot to have something reliable. |
|
*/ |
|
|
static int |
static int |
find_offset(LINE *lp, int off) |
find_absolute_dot(LINE *lp, int off) |
{ |
{ |
int count = 0; |
int count = 0; |
LINE *p; |
LINE *p; |
|
|
if (count != 0) { |
if (count != 0) { |
if (p == curwp->w_linep) { |
if (p == curwp->w_linep) { |
ewprintf("Error: Undo stuff called with a" |
ewprintf("Error: Undo stuff called with a" |
"nonexistent line\n"); |
"nonexistent line"); |
return FALSE; |
return FALSE; |
} |
} |
} |
} |
|
|
} |
} |
|
|
static int |
static int |
find_linep(int pos, LINE **olp, int *offset) |
find_line_offset(int pos, LINE **olp, int *offset) |
{ |
{ |
LINE *p; |
LINE *p; |
|
|
p = curwp->w_linep; |
p = curwp->w_linep; |
while (pos > 0 && pos > llength(p)) { |
while (pos > llength(p)) { |
pos -= llength(p) + 1; |
pos -= llength(p) + 1; |
if ((p = lforw(p)) == curwp->w_linep) { |
if ((p = lforw(p)) == curwp->w_linep) { |
*olp = NULL; |
*olp = NULL; |
|
|
struct undo_rec *rec; |
struct undo_rec *rec; |
|
|
rec = LIST_FIRST(&undo_free); |
rec = LIST_FIRST(&undo_free); |
if (rec != NULL) |
if (rec != NULL) { |
LIST_REMOVE(rec, next); /* Remove it from the free-list */ |
LIST_REMOVE(rec, next); /* Remove it from the free-list */ |
else { |
undo_free_num--; |
|
} else { |
if ((rec = malloc(sizeof *rec)) == NULL) |
if ((rec = malloc(sizeof *rec)) == NULL) |
panic("Out of memory in undo code (record)"); |
panic("Out of memory in undo code (record)"); |
} |
} |
memset(rec, 0, sizeof(struct undo_rec)); |
memset(rec, 0, sizeof(struct undo_rec)); |
|
|
return rec; |
return rec; |
} |
} |
|
|
void |
void |
free_undo_record(struct undo_rec *rec) |
free_undo_record(struct undo_rec *rec) |
{ |
{ |
|
static int initialised = 0; |
|
|
|
/* |
|
* On the first run, do initialisation of the free list. |
|
*/ |
|
if (initialised == 0) { |
|
LIST_INIT(&undo_free); |
|
initialised = 1; |
|
} |
if (rec->content != NULL) { |
if (rec->content != NULL) { |
free(rec->content); |
free(rec->content); |
rec->content = NULL; |
rec->content = NULL; |
|
|
return; |
return; |
} |
} |
undo_free_num++; |
undo_free_num++; |
|
|
LIST_INSERT_HEAD(&undo_free, rec, next); |
LIST_INSERT_HEAD(&undo_free, rec, next); |
} |
} |
|
|
|
|
{ |
{ |
struct undo_rec *rec; |
struct undo_rec *rec; |
|
|
rec = LIST_END(&undo_list); |
rec = LIST_END(&curbp->b_undo); |
if (rec != NULL) { |
if (rec != NULL) { |
undo_free_num--; |
undo_free_num--; |
LIST_REMOVE(rec, next); /* Remove it from the undo_list before |
LIST_REMOVE(rec, next); |
* we insert it in the free list */ |
|
free_undo_record(rec); |
free_undo_record(rec); |
return 1; |
return 1; |
} |
} |
return 0; |
return 0; |
} |
} |
|
|
int |
static __inline__ int |
undo_init(void) |
last_was_boundary() |
{ |
{ |
LIST_INIT(&undo_free); |
struct undo_rec *rec; |
|
|
return TRUE; |
if ((rec = LIST_FIRST(&curbp->b_undo)) != NULL && |
|
(rec->type == BOUNDARY)) |
|
return 1; |
|
return 0; |
} |
} |
|
|
int |
int |
undo_enable(int on) |
undo_enable(int on) |
{ |
{ |
undo_disable_flag = on ? 0 : 1; |
undo_disable_flag = on ? 0 : 1; |
|
|
/* |
|
* XXX-Vince: |
|
* |
|
* Here, I wonder if we should assume that the user has made a |
|
* long term choice. If so, we could free all internal undo |
|
* data and save memory. |
|
*/ |
|
|
|
return on; |
return on; |
} |
} |
|
|
int |
int |
undo_add_custom(int type, LINE *lp, int offset, void *content, int size) |
undo_add_boundary(void) |
{ |
{ |
struct undo_rec *rec; |
struct undo_rec *rec; |
|
|
if (undo_disable_flag) |
|
return TRUE; |
|
rec = new_undo_record(); |
rec = new_undo_record(); |
rec->pos = find_offset(lp, offset); |
rec->type = BOUNDARY; |
rec->type = type; |
|
rec->content = content; |
|
rec->region.r_linep = lp; |
|
rec->region.r_offset = offset; |
|
rec->region.r_size = size; |
|
|
|
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
|
|
return TRUE; |
return TRUE; |
} |
} |
|
|
|
/* |
|
* If asocial is true, we arrange for this record to be let alone. forever. |
|
* Yes, this is a bit of a hack... |
|
*/ |
int |
int |
undo_add_boundary(void) |
undo_add_custom(int asocial, |
|
int type, LINE *lp, int offset, void *content, int size) |
{ |
{ |
struct undo_rec *rec; |
struct undo_rec *rec; |
|
|
if (undo_disable_flag) |
if (undo_disable_flag) |
return TRUE; |
return TRUE; |
|
|
rec = new_undo_record(); |
rec = new_undo_record(); |
rec->type = BOUNDARY; |
if (lp != NULL) |
|
rec->pos = find_absolute_dot(lp, offset); |
|
else |
|
rec->pos = 0; |
|
rec->type = type; |
|
rec->content = content; |
|
rec->region.r_linep = lp; |
|
rec->region.r_offset = offset; |
|
rec->region.r_size = size; |
|
|
|
if (!last_was_boundary()) |
|
undo_add_boundary(); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
|
undo_add_boundary(); |
|
if (asocial) /* Add a second one */ |
|
undo_add_boundary(); |
|
|
return TRUE; |
return TRUE; |
} |
} |
|
|
|
|
{ |
{ |
REGION reg; |
REGION reg; |
struct undo_rec *rec; |
struct undo_rec *rec; |
|
int pos; |
|
|
if (undo_disable_flag) |
if (undo_disable_flag) |
return TRUE; |
return TRUE; |
|
|
reg.r_linep = lp; |
reg.r_linep = lp; |
reg.r_offset = offset; |
reg.r_offset = offset; |
reg.r_size = size; |
reg.r_size = size; |
|
|
|
pos = find_absolute_dot(lp, offset); |
|
|
/* |
/* |
* We try to reuse the last undo record to `compress' things. |
* We try to reuse the last undo record to `compress' things. |
*/ |
*/ |
rec = LIST_FIRST(&curbp->b_undo); |
rec = LIST_FIRST(&curbp->b_undo); |
if ((rec != NULL) && |
/* this will be hit like, 80% of the time... */ |
(rec->type == INSERT) && |
if (rec != NULL && rec->type == BOUNDARY) |
(rec->region.r_linep == lp)) { |
rec = LIST_NEXT(rec, next); |
int dist; |
|
|
|
dist = rec->region.r_offset - reg.r_offset; |
if ((rec != NULL) && |
|
(rec->type == INSERT)) { |
if (rec->region.r_size >= dist) { |
if (rec->pos + rec->region.r_size == pos) { |
rec->region.r_size += reg.r_size; |
rec->region.r_size += reg.r_size; |
return TRUE; |
return TRUE; |
} |
} |
} |
} |
|
|
/* |
/* |
* We couldn't reuse the last undo record, so prepare a new one |
* We couldn't reuse the last undo record, so prepare a new one |
*/ |
*/ |
rec = new_undo_record(); |
rec = new_undo_record(); |
rec->pos = find_offset(lp, offset); |
rec->pos = pos; |
rec->type = INSERT; |
rec->type = INSERT; |
memmove(&rec->region, ®, sizeof(REGION)); |
memmove(&rec->region, ®, sizeof(REGION)); |
rec->content = NULL; |
rec->content = NULL; |
|
|
|
if (!last_was_boundary()) |
|
undo_add_boundary(); |
|
|
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
|
undo_add_boundary(); |
|
|
return TRUE; |
return TRUE; |
} |
} |
|
|
{ |
{ |
REGION reg; |
REGION reg; |
struct undo_rec *rec; |
struct undo_rec *rec; |
int dist, pos, skip = 0; |
int pos; |
|
|
if (undo_disable_flag) |
if (undo_disable_flag) |
return TRUE; |
return TRUE; |
|
|
reg.r_offset = offset; |
reg.r_offset = offset; |
reg.r_size = size; |
reg.r_size = size; |
|
|
pos = find_offset(lp, offset); |
pos = find_absolute_dot(lp, offset); |
|
|
if (size == 1 && llength(lp) == 0) { |
if (offset == llength(lp)) /* if it's a newline... */ |
skip = 1; |
undo_add_boundary(); |
|
else if ((rec = LIST_FIRST(&curbp->b_undo)) != NULL) { |
|
/* |
|
* Separate this command from the previous one if we're not |
|
* just before the previous record... |
|
*/ |
|
if (rec->type == DELETE){ |
|
if (rec->pos - rec->region.r_size != pos) |
|
undo_add_boundary(); |
|
} else if (rec->type != BOUNDARY) |
|
undo_add_boundary(); |
} |
} |
|
|
/* |
|
* Again, try to reuse last undo record, if we can |
|
*/ |
|
rec = LIST_FIRST(&curbp->b_undo); |
|
if (!skip && |
|
(rec != NULL) && |
|
(rec->type == DELETE) && |
|
(rec->region.r_linep == reg.r_linep)) { |
|
char *newbuf; |
|
int newlen; |
|
|
|
dist = rec->region.r_offset - reg.r_offset; |
|
if (rec->region.r_size >= dist) { |
|
newlen = rec->region.r_size + reg.r_size; |
|
|
|
do { |
|
newbuf = malloc(newlen * sizeof(char)); |
|
} while (newbuf == NULL && drop_oldest_undo_record()); |
|
|
|
if (newbuf == NULL) |
|
panic("out of memory in undo delete code"); |
|
|
|
/* |
|
* [new data][old data] |
|
*/ |
|
region_get_data(®, newbuf, size); |
|
memmove(newbuf + reg.r_size, rec->content, |
|
rec->region.r_size); |
|
|
|
rec->pos = pos; |
|
rec->region.r_offset = reg.r_offset; |
|
rec->region.r_size = newlen; |
|
if (rec->content != NULL) |
|
free(rec->content); |
|
rec->content = newbuf; |
|
|
|
return TRUE; |
|
} |
|
} |
|
|
|
/* |
|
* So we couldn't reuse the last undo record? Just allocate a new |
|
* one. |
|
*/ |
|
rec = new_undo_record(); |
rec = new_undo_record(); |
rec->pos = pos; |
rec->pos = pos; |
|
|
|
|
do { |
do { |
rec->content = malloc(reg.r_size + 1); |
rec->content = malloc(reg.r_size + 1); |
} while ((rec->content == NULL) && drop_oldest_undo_record()); |
} while ((rec->content == NULL) && drop_oldest_undo_record()); |
|
|
if (rec->content == NULL) |
if (rec->content == NULL) |
panic("Out of memory"); |
panic("Out of memory"); |
|
|
region_get_data(®, rec->content, reg.r_size); |
region_get_data(®, rec->content, reg.r_size); |
|
|
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
|
undo_add_boundary(); |
|
|
return TRUE; |
return TRUE; |
} |
} |
|
|
REGION reg; |
REGION reg; |
struct undo_rec *rec; |
struct undo_rec *rec; |
|
|
|
|
if (undo_disable_flag) |
if (undo_disable_flag) |
return TRUE; |
return TRUE; |
|
|
reg.r_linep = lp; |
reg.r_linep = lp; |
reg.r_offset = offset; |
reg.r_offset = offset; |
reg.r_size = size; |
reg.r_size = size; |
|
|
rec = new_undo_record(); |
rec = new_undo_record(); |
rec->pos = find_offset(lp, offset); |
rec->pos = find_absolute_dot(lp, offset); |
rec->type = CHANGE; |
rec->type = CHANGE; |
memmove(&rec->region, ®, sizeof reg); |
memmove(&rec->region, ®, sizeof reg); |
|
|
|
/* |
|
* Try to allocate a buffer for the changed data. |
|
*/ |
do { |
do { |
rec->content = malloc(size + 1); |
rec->content = malloc(size + 1); |
} while ((rec->content == NULL) && drop_oldest_undo_record()); |
} while ((rec->content == NULL) && drop_oldest_undo_record()); |
|
|
if (rec->content == NULL) |
if (rec->content == NULL) |
panic("Out of memory in undo change code"); |
panic("Out of memory in undo change code"); |
|
|
region_get_data(®, rec->content, size); |
region_get_data(®, rec->content, size); |
|
|
|
if (!last_was_boundary()) |
|
undo_add_boundary(); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
LIST_INSERT_HEAD(&curbp->b_undo, rec, next); |
|
undo_add_boundary(); |
|
|
return TRUE; |
return TRUE; |
} |
} |
|
|
|
|
*/ |
*/ |
if ((bp = bfind("*undo*", TRUE)) == NULL) |
if ((bp = bfind("*undo*", TRUE)) == NULL) |
return FALSE; |
return FALSE; |
|
bp->b_flag |= BFREADONLY; |
bclear(bp); |
bclear(bp); |
popbuf(bp); |
popbuf(bp); |
|
|
|
|
return TRUE; |
return TRUE; |
} |
} |
|
|
|
/* |
|
* After the user did action1, then action2, then action3 : |
|
* |
|
* [action3] <--- Undoptr |
|
* [action2] |
|
* [action1] |
|
* ------ |
|
* [undo] |
|
* |
|
* After undo: |
|
* |
|
* [undo of action3] |
|
* [action2] <--- Undoptr |
|
* [action1] |
|
* ------ |
|
* [undo] |
|
* |
|
* After another undo: |
|
* |
|
* |
|
* [undo of action2] |
|
* [undo of action3] |
|
* [action1] <--- Undoptr |
|
* ------ |
|
* [undo] |
|
* |
|
* Note that the "undo of actionX" have no special meaning. Only when, |
|
* say, we undo a deletion, the insertion will be recorded just as if it |
|
* was typed on the keyboard. Hence resulting in the inverse operation to be |
|
* saved in the list. |
|
* |
|
* If undoptr reaches the bottom of the list, or if we moved between |
|
* two undo actions, we make it point back at the topmost record. This is |
|
* how we handle redoing. |
|
*/ |
int |
int |
undo(int f, int n) |
undo(int f, int n) |
{ |
{ |
struct undo_rec *rec; |
struct undo_rec *ptr, *nptr; |
LINE *ln; |
int done, rval; |
int off; |
LINE *lp; |
|
int offset; |
|
|
/* |
ptr = curbp->b_undoptr; |
* Let called functions know they are below us (for |
|
* example, ldelete don't want to record an undo record |
|
* when called by us) |
|
*/ |
|
undoaction++; |
|
|
|
|
/* if we moved, make ptr point back to the top of the list */ |
|
if ((curbp->b_undopos.r_linep != curwp->w_dotp) || |
|
(curbp->b_undopos.r_offset != curwp->w_doto) || |
|
(ptr == NULL)) |
|
ptr = LIST_FIRST(&curbp->b_undo); |
|
|
|
rval = TRUE; |
while (n > 0) { |
while (n > 0) { |
rec = LIST_FIRST(&curbp->b_undo); |
/* if we have a spurious boundary, free it and move on.... */ |
if (rec == NULL) { |
while (ptr && ptr->type == BOUNDARY) { |
ewprintf("Nothing to undo!"); |
nptr = LIST_NEXT(ptr, next); |
return FALSE; |
LIST_REMOVE(ptr, next); |
|
free_undo_record(ptr); |
|
ptr = nptr; |
} |
} |
LIST_REMOVE(rec, next); |
|
if (rec->type == BOUNDARY) { |
|
continue; |
|
} |
|
|
|
find_linep(rec->pos, &ln, &off); |
|
if (ln == NULL) |
|
return FALSE; |
|
|
|
/* |
/* |
* Move to where this record has to apply |
* Ptr is NULL, but on the next run, it will point to the |
|
* top again, redoing all stuff done in the buffer since |
|
* its creation. |
*/ |
*/ |
curwp->w_dotp = ln; |
if (ptr == NULL) { |
curwp->w_doto = off; |
ewprintf("Nothing to undo!"); |
|
rval = FALSE; |
switch (rec->type) { |
|
case INSERT: |
|
ldelete(rec->region.r_size, KFORW); |
|
break; |
break; |
case DELETE: |
|
region_put_data(rec->content, rec->region.r_size); |
|
break; |
|
case CHANGE: |
|
forwchar(0, rec->region.r_size); |
|
lreplace(rec->region.r_size, rec->content, 1); |
|
break; |
|
default: |
|
break; |
|
} |
} |
|
|
free_undo_record(rec); |
|
|
|
|
/* |
|
* Loop while we don't get a boundary specifying we've |
|
* finished the current action... |
|
*/ |
|
done = 0; |
|
do { |
|
/* Unlink the current node from the list */ |
|
nptr = LIST_NEXT(ptr, next); |
|
LIST_REMOVE(ptr, next); |
|
|
|
/* |
|
* Move to where this has to apply |
|
* |
|
* Boundaries are put as position 0 (to save |
|
* lookup time in find_absolute_dot) so we must |
|
* not move there... |
|
*/ |
|
if (ptr->type != BOUNDARY) { |
|
if (find_line_offset(ptr->pos,&lp,&offset) |
|
== FALSE) { |
|
ewprintf("Internal error in Undo!"); |
|
rval = FALSE; |
|
break; |
|
} |
|
curwp->w_dotp = lp; |
|
curwp->w_doto = offset; |
|
} |
|
|
|
/* |
|
* Do operation^-1 |
|
*/ |
|
switch (ptr->type) { |
|
case INSERT: |
|
ldelete(ptr->region.r_size, KFORW); |
|
break; |
|
case DELETE: |
|
region_put_data(ptr->content, |
|
ptr->region.r_size); |
|
break; |
|
case CHANGE: |
|
forwchar(0, ptr->region.r_size); |
|
lreplace(ptr->region.r_size, |
|
ptr->content, 1); |
|
break; |
|
case BOUNDARY: |
|
done = 1; |
|
break; |
|
default: |
|
break; |
|
} |
|
free_undo_record(ptr); |
|
|
|
/* And move to next record */ |
|
ptr = nptr; |
|
} while (ptr != NULL && !done); |
|
|
|
ewprintf("Undo!"); |
n--; |
n--; |
} |
} |
undoaction--; |
/* |
|
* Record where we are. (we have to save our new position at the end |
return TRUE; |
* since we change the dot when undoing....) |
|
*/ |
|
curbp->b_undoptr = ptr; |
|
curbp->b_undopos.r_linep = curwp->w_dotp; |
|
curbp->b_undopos.r_offset = curwp->w_doto; |
|
|
|
return rval; |
} |
} |