Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/undo.c
- This file was added.
| /* $OpenBSD: undo.c,v 1.58 2016/09/05 08:10:58 lum Exp $ */ | |||||
| /* | |||||
| * This file is in the public domain | |||||
| */ | |||||
| #include <sys/queue.h> | |||||
| #include <signal.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #include "def.h" | |||||
| #include "kbd.h" | |||||
| #define MAX_FREE_RECORDS 32 | |||||
| /* | |||||
| * Local variables | |||||
| */ | |||||
| static struct undoq undo_free; | |||||
| static int undo_free_num; | |||||
| static int boundary_flag = TRUE; | |||||
| static int undo_enable_flag = TRUE; | |||||
| /* | |||||
| * Local functions | |||||
| */ | |||||
| static int find_dot(struct line *, int); | |||||
| static int find_lo(int, struct line **, int *, int *); | |||||
| static struct undo_rec *new_undo_record(void); | |||||
| static int drop_oldest_undo_record(void); | |||||
| /* | |||||
| * find_dot, find_lo() | |||||
| * | |||||
| * 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 | |||||
| find_dot(struct line *lp, int off) | |||||
| { | |||||
| int count = 0; | |||||
| struct line *p; | |||||
| for (p = curbp->b_headp; p != lp; p = lforw(p)) { | |||||
| if (count != 0) { | |||||
| if (p == curbp->b_headp) { | |||||
| dobeep(); | |||||
| ewprintf("Error: Undo stuff called with a" | |||||
| "nonexistent line"); | |||||
| return (FALSE); | |||||
| } | |||||
| } | |||||
| count += llength(p) + 1; | |||||
| } | |||||
| count += off; | |||||
| return (count); | |||||
| } | |||||
| static int | |||||
| find_lo(int pos, struct line **olp, int *offset, int *lnum) | |||||
| { | |||||
| struct line *p; | |||||
| int lineno; | |||||
| p = curbp->b_headp; | |||||
| lineno = 0; | |||||
| while (pos > llength(p)) { | |||||
| pos -= llength(p) + 1; | |||||
| if ((p = lforw(p)) == curbp->b_headp) { | |||||
| *olp = NULL; | |||||
| *offset = 0; | |||||
| return (FALSE); | |||||
| } | |||||
| lineno++; | |||||
| } | |||||
| *olp = p; | |||||
| *offset = pos; | |||||
| *lnum = lineno; | |||||
| return (TRUE); | |||||
| } | |||||
| static struct undo_rec * | |||||
| new_undo_record(void) | |||||
| { | |||||
| struct undo_rec *rec; | |||||
| rec = TAILQ_FIRST(&undo_free); | |||||
| if (rec != NULL) { | |||||
| /* Remove it from the free-list */ | |||||
| TAILQ_REMOVE(&undo_free, rec, next); | |||||
| undo_free_num--; | |||||
| } else { | |||||
| if ((rec = malloc(sizeof(*rec))) == NULL) | |||||
| panic("Out of memory in undo code (record)"); | |||||
| } | |||||
| memset(rec, 0, sizeof(struct undo_rec)); | |||||
| return (rec); | |||||
| } | |||||
| void | |||||
| free_undo_record(struct undo_rec *rec) | |||||
| { | |||||
| static int initialised = 0; | |||||
| /* | |||||
| * On the first run, do initialisation of the free list. | |||||
| */ | |||||
| if (initialised == 0) { | |||||
| TAILQ_INIT(&undo_free); | |||||
| initialised = 1; | |||||
| } | |||||
| free(rec->content); | |||||
| rec->content = NULL; | |||||
| if (undo_free_num >= MAX_FREE_RECORDS) { | |||||
| free(rec); | |||||
| return; | |||||
| } | |||||
| undo_free_num++; | |||||
| TAILQ_INSERT_HEAD(&undo_free, rec, next); | |||||
| } | |||||
| /* | |||||
| * Drop the oldest undo record in our list. Return 1 if we could remove it, | |||||
| * 0 if the undo list was empty. | |||||
| */ | |||||
| static int | |||||
| drop_oldest_undo_record(void) | |||||
| { | |||||
| struct undo_rec *rec; | |||||
| rec = TAILQ_LAST(&curbp->b_undo, undoq); | |||||
| if (rec != NULL) { | |||||
| undo_free_num--; | |||||
| TAILQ_REMOVE(&curbp->b_undo, rec, next); | |||||
| free_undo_record(rec); | |||||
| return (1); | |||||
| } | |||||
| return (0); | |||||
| } | |||||
| static int | |||||
| lastrectype(void) | |||||
| { | |||||
| struct undo_rec *rec; | |||||
| if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) | |||||
| return (rec->type); | |||||
| return (0); | |||||
| } | |||||
| /* | |||||
| * Returns TRUE if undo is enabled, FALSE otherwise. | |||||
| */ | |||||
| int | |||||
| undo_enabled(void) | |||||
| { | |||||
| return (undo_enable_flag); | |||||
| } | |||||
| /* | |||||
| * undo_enable: toggle undo_enable. | |||||
| * Returns the previous value of the flag. | |||||
| */ | |||||
| int | |||||
| undo_enable(int f, int n) | |||||
| { | |||||
| int pon = undo_enable_flag; | |||||
| if (f & (FFARG | FFRAND)) | |||||
| undo_enable_flag = n > 0; | |||||
| else | |||||
| undo_enable_flag = !undo_enable_flag; | |||||
| if (!(f & FFRAND)) | |||||
| ewprintf("Undo %sabled", undo_enable_flag ? "en" : "dis"); | |||||
| return (pon); | |||||
| } | |||||
| /* | |||||
| * If undo is enabled, then: | |||||
| * Toggle undo boundary recording. | |||||
| * If called with an argument, (n > 0) => enable. Otherwise disable. | |||||
| * In either case, add an undo boundary | |||||
| * If undo is disabled, this function has no effect. | |||||
| */ | |||||
| int | |||||
| undo_boundary_enable(int f, int n) | |||||
| { | |||||
| int bon = boundary_flag; | |||||
| if (!undo_enable_flag) | |||||
| return (FALSE); | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| if (f & (FFARG | FFRAND)) | |||||
| boundary_flag = n > 0; | |||||
| else | |||||
| boundary_flag = !boundary_flag; | |||||
| if (!(f & FFRAND)) | |||||
| ewprintf("Undo boundaries %sabled", | |||||
| boundary_flag ? "en" : "dis"); | |||||
| return (bon); | |||||
| } | |||||
| /* | |||||
| * Record an undo boundary, unless boundary_flag == FALSE. | |||||
| * Does nothing if previous undo entry is already a boundary or 'modified' flag. | |||||
| */ | |||||
| int | |||||
| undo_add_boundary(int f, int n) | |||||
| { | |||||
| struct undo_rec *rec; | |||||
| int last; | |||||
| if (boundary_flag == FALSE) | |||||
| return (FALSE); | |||||
| last = lastrectype(); | |||||
| if (last == BOUNDARY || last == MODIFIED) | |||||
| return (TRUE); | |||||
| rec = new_undo_record(); | |||||
| rec->type = BOUNDARY; | |||||
| TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); | |||||
| return (TRUE); | |||||
| } | |||||
| /* | |||||
| * Record an undo "modified" boundary | |||||
| */ | |||||
| void | |||||
| undo_add_modified(void) | |||||
| { | |||||
| struct undo_rec *rec, *trec; | |||||
| TAILQ_FOREACH_SAFE(rec, &curbp->b_undo, next, trec) | |||||
| if (rec->type == MODIFIED) { | |||||
| TAILQ_REMOVE(&curbp->b_undo, rec, next); | |||||
| free_undo_record(rec); | |||||
| } | |||||
| rec = new_undo_record(); | |||||
| rec->type = MODIFIED; | |||||
| TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); | |||||
| return; | |||||
| } | |||||
| int | |||||
| undo_add_insert(struct line *lp, int offset, int size) | |||||
| { | |||||
| struct region reg; | |||||
| struct undo_rec *rec; | |||||
| int pos; | |||||
| if (!undo_enable_flag) | |||||
| return (TRUE); | |||||
| memset(®, 0, sizeof(reg)); | |||||
| reg.r_linep = lp; | |||||
| reg.r_offset = offset; | |||||
| reg.r_size = size; | |||||
| pos = find_dot(lp, offset); | |||||
| /* | |||||
| * We try to reuse the last undo record to `compress' things. | |||||
| */ | |||||
| rec = TAILQ_FIRST(&curbp->b_undo); | |||||
| if (rec != NULL && rec->type == INSERT) { | |||||
| if (rec->pos + rec->region.r_size == pos) { | |||||
| rec->region.r_size += reg.r_size; | |||||
| return (TRUE); | |||||
| } | |||||
| } | |||||
| /* | |||||
| * We couldn't reuse the last undo record, so prepare a new one. | |||||
| */ | |||||
| rec = new_undo_record(); | |||||
| rec->pos = pos; | |||||
| rec->type = INSERT; | |||||
| memmove(&rec->region, ®, sizeof(struct region)); | |||||
| rec->content = NULL; | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); | |||||
| return (TRUE); | |||||
| } | |||||
| /* | |||||
| * This of course must be done _before_ the actual deletion is done. | |||||
| */ | |||||
| int | |||||
| undo_add_delete(struct line *lp, int offset, int size, int isreg) | |||||
| { | |||||
| struct region reg; | |||||
| struct undo_rec *rec; | |||||
| int pos; | |||||
| if (!undo_enable_flag) | |||||
| return (TRUE); | |||||
| memset(®, 0, sizeof(reg)); | |||||
| reg.r_linep = lp; | |||||
| reg.r_offset = offset; | |||||
| reg.r_size = size; | |||||
| pos = find_dot(lp, offset); | |||||
| if (offset == llength(lp)) /* if it's a newline... */ | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| else if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) { | |||||
| /* | |||||
| * Separate this command from the previous one if we're not | |||||
| * just before the previous record... | |||||
| */ | |||||
| if (!isreg && rec->type == DELETE) { | |||||
| if (rec->pos - rec->region.r_size != pos) | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| } | |||||
| } | |||||
| rec = new_undo_record(); | |||||
| rec->pos = pos; | |||||
| if (isreg) | |||||
| rec->type = DELREG; | |||||
| else | |||||
| rec->type = DELETE; | |||||
| memmove(&rec->region, ®, sizeof(struct region)); | |||||
| do { | |||||
| rec->content = malloc(reg.r_size + 1); | |||||
| } while ((rec->content == NULL) && drop_oldest_undo_record()); | |||||
| if (rec->content == NULL) | |||||
| panic("Out of memory"); | |||||
| region_get_data(®, rec->content, reg.r_size); | |||||
| if (isreg || lastrectype() != DELETE) | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); | |||||
| return (TRUE); | |||||
| } | |||||
| /* | |||||
| * This of course must be called before the change takes place. | |||||
| */ | |||||
| int | |||||
| undo_add_change(struct line *lp, int offset, int size) | |||||
| { | |||||
| if (!undo_enable_flag) | |||||
| return (TRUE); | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| boundary_flag = FALSE; | |||||
| undo_add_delete(lp, offset, size, 0); | |||||
| undo_add_insert(lp, offset, size); | |||||
| boundary_flag = TRUE; | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| return (TRUE); | |||||
| } | |||||
| /* | |||||
| * Show the undo records for the current buffer in a new buffer. | |||||
| */ | |||||
| /* ARGSUSED */ | |||||
| int | |||||
| undo_dump(int f, int n) | |||||
| { | |||||
| struct undo_rec *rec; | |||||
| struct buffer *bp; | |||||
| struct mgwin *wp; | |||||
| char buf[4096], tmp[1024]; | |||||
| int num; | |||||
| /* | |||||
| * Prepare the buffer for insertion. | |||||
| */ | |||||
| if ((bp = bfind("*undo*", TRUE)) == NULL) | |||||
| return (FALSE); | |||||
| bp->b_flag |= BFREADONLY; | |||||
| bclear(bp); | |||||
| if ((wp = popbuf(bp, WNONE)) == NULL) | |||||
| return (FALSE); | |||||
| for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { | |||||
| if (wp->w_bufp == bp) { | |||||
| wp->w_dotp = bp->b_headp; | |||||
| wp->w_doto = 0; | |||||
| } | |||||
| } | |||||
| num = 0; | |||||
| TAILQ_FOREACH(rec, &curbp->b_undo, next) { | |||||
| num++; | |||||
| snprintf(buf, sizeof(buf), | |||||
| "%d:\t %s at %d ", num, | |||||
| (rec->type == DELETE) ? "DELETE": | |||||
| (rec->type == DELREG) ? "DELREGION": | |||||
| (rec->type == INSERT) ? "INSERT": | |||||
| (rec->type == BOUNDARY) ? "----" : | |||||
| (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN", | |||||
| rec->pos); | |||||
| if (rec->content) { | |||||
| (void)strlcat(buf, "\"", sizeof(buf)); | |||||
| snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size, | |||||
| rec->content); | |||||
| (void)strlcat(buf, tmp, sizeof(buf)); | |||||
| (void)strlcat(buf, "\"", sizeof(buf)); | |||||
| } | |||||
| snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size); | |||||
| if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) { | |||||
| dobeep(); | |||||
| ewprintf("Undo record too large. Aborted."); | |||||
| return (FALSE); | |||||
| } | |||||
| addlinef(bp, "%s", buf); | |||||
| } | |||||
| for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { | |||||
| if (wp->w_bufp == bp) { | |||||
| wp->w_dotline = num+1; | |||||
| wp->w_rflag |= WFFULL; | |||||
| } | |||||
| } | |||||
| 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 | |||||
| * we undo a deletion, the insertion will be recorded just as if it | |||||
| * was typed on the keyboard. Resulting in the inverse operation being | |||||
| * 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. | |||||
| */ | |||||
| /* ARGSUSED */ | |||||
| int | |||||
| undo(int f, int n) | |||||
| { | |||||
| struct undo_rec *ptr, *nptr; | |||||
| int done, rval; | |||||
| struct line *lp; | |||||
| int offset, save; | |||||
| static int nulled = FALSE; | |||||
| int lineno; | |||||
| if (n < 0) | |||||
| return (FALSE); | |||||
| ptr = curbp->b_undoptr; | |||||
| /* first invocation, make ptr point back to the top of the list */ | |||||
| if ((ptr == NULL && nulled == TRUE) || rptcount == 0) { | |||||
| ptr = TAILQ_FIRST(&curbp->b_undo); | |||||
| nulled = TRUE; | |||||
| } | |||||
| rval = TRUE; | |||||
| while (n--) { | |||||
| /* if we have a spurious boundary, free it and move on.... */ | |||||
| while (ptr && ptr->type == BOUNDARY) { | |||||
| nptr = TAILQ_NEXT(ptr, next); | |||||
| TAILQ_REMOVE(&curbp->b_undo, ptr, next); | |||||
| free_undo_record(ptr); | |||||
| ptr = nptr; | |||||
| } | |||||
| /* | |||||
| * 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. | |||||
| */ | |||||
| if (ptr == NULL) { | |||||
| dobeep(); | |||||
| ewprintf("No further undo information"); | |||||
| rval = FALSE; | |||||
| nulled = TRUE; | |||||
| break; | |||||
| } | |||||
| nulled = FALSE; | |||||
| /* | |||||
| * Loop while we don't get a boundary specifying we've | |||||
| * finished the current action... | |||||
| */ | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| save = boundary_flag; | |||||
| boundary_flag = FALSE; | |||||
| done = 0; | |||||
| do { | |||||
| /* | |||||
| * Move to where this has to apply | |||||
| * | |||||
| * Boundaries (and the modified flag) are put as | |||||
| * position 0 (to save lookup time in find_dot) | |||||
| * so we must not move there... | |||||
| */ | |||||
| if (ptr->type != BOUNDARY && ptr->type != MODIFIED) { | |||||
| if (find_lo(ptr->pos, &lp, | |||||
| &offset, &lineno) == FALSE) { | |||||
| dobeep(); | |||||
| ewprintf("Internal error in Undo!"); | |||||
| rval = FALSE; | |||||
| break; | |||||
| } | |||||
| curwp->w_dotp = lp; | |||||
| curwp->w_doto = offset; | |||||
| curwp->w_markline = curwp->w_dotline; | |||||
| curwp->w_dotline = lineno; | |||||
| } | |||||
| /* | |||||
| * Do operation^-1 | |||||
| */ | |||||
| switch (ptr->type) { | |||||
| case INSERT: | |||||
| ldelete(ptr->region.r_size, KNONE); | |||||
| break; | |||||
| case DELETE: | |||||
| lp = curwp->w_dotp; | |||||
| offset = curwp->w_doto; | |||||
| region_put_data(ptr->content, | |||||
| ptr->region.r_size); | |||||
| curwp->w_dotp = lp; | |||||
| curwp->w_doto = offset; | |||||
| break; | |||||
| case DELREG: | |||||
| region_put_data(ptr->content, | |||||
| ptr->region.r_size); | |||||
| break; | |||||
| case BOUNDARY: | |||||
| done = 1; | |||||
| break; | |||||
| case MODIFIED: | |||||
| curbp->b_flag &= ~BFCHG; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| /* And move to next record */ | |||||
| ptr = TAILQ_NEXT(ptr, next); | |||||
| } while (ptr != NULL && !done); | |||||
| boundary_flag = save; | |||||
| undo_add_boundary(FFRAND, 1); | |||||
| ewprintf("Undo!"); | |||||
| } | |||||
| curbp->b_undoptr = ptr; | |||||
| return (rval); | |||||
| } | |||||