Annotation of src/usr.bin/mg/undo.c, Revision 1.45
1.45 ! kjell 1: /* $OpenBSD: undo.c,v 1.44 2007/09/16 20:34:18 kjell Exp $ */
1.1 vincent 2: /*
1.45 ! kjell 3: * This file is in the public domain
1.1 vincent 4: */
5:
6: #include "def.h"
7: #include "kbd.h"
8:
9: #define MAX_FREE_RECORDS 32
10:
11: /*
12: * Local variables
13: */
1.7 vincent 14: static LIST_HEAD(, undo_rec) undo_free;
15: static int undo_free_num;
1.42 kjell 16: static int boundary_flag = TRUE;
1.43 deraadt 17: static int undo_enable_flag = TRUE;
1.1 vincent 18:
19: /*
20: * Local functions
21: */
1.35 deraadt 22: static int find_dot(struct line *, int);
1.39 kjell 23: static int find_lo(int, struct line **, int *, int *);
1.1 vincent 24: static struct undo_rec *new_undo_record(void);
25: static int drop_oldest_undo_record(void);
26:
1.9 vincent 27: /*
1.24 vincent 28: * find_dot, find_lo()
1.9 vincent 29: *
30: * Find an absolute dot in the buffer from a line/offset pair, and vice-versa.
31: *
1.13 deraadt 32: * Since lines can be deleted while they are referenced by undo record, we
1.9 vincent 33: * need to have an absolute dot to have something reliable.
34: */
1.1 vincent 35: static int
1.35 deraadt 36: find_dot(struct line *lp, int off)
1.1 vincent 37: {
1.25 db 38: int count = 0;
1.35 deraadt 39: struct line *p;
1.1 vincent 40:
1.41 kjell 41: for (p = curbp->b_headp; p != lp; p = lforw(p)) {
1.1 vincent 42: if (count != 0) {
1.41 kjell 43: if (p == curbp->b_headp) {
1.1 vincent 44: ewprintf("Error: Undo stuff called with a"
1.9 vincent 45: "nonexistent line");
1.11 vincent 46: return (FALSE);
1.1 vincent 47: }
48: }
49: count += llength(p) + 1;
50: }
51: count += off;
52:
1.11 vincent 53: return (count);
1.1 vincent 54: }
55:
56: static int
1.39 kjell 57: find_lo(int pos, struct line **olp, int *offset, int *lnum)
1.1 vincent 58: {
1.35 deraadt 59: struct line *p;
1.39 kjell 60: int lineno;
1.1 vincent 61:
1.41 kjell 62: p = curbp->b_headp;
1.39 kjell 63: lineno = 0;
1.9 vincent 64: while (pos > llength(p)) {
1.1 vincent 65: pos -= llength(p) + 1;
1.41 kjell 66: if ((p = lforw(p)) == curbp->b_headp) {
1.1 vincent 67: *olp = NULL;
68: *offset = 0;
1.11 vincent 69: return (FALSE);
1.1 vincent 70: }
1.39 kjell 71: lineno++;
1.1 vincent 72: }
73: *olp = p;
74: *offset = pos;
1.39 kjell 75: *lnum = lineno;
1.1 vincent 76:
1.11 vincent 77: return (TRUE);
1.1 vincent 78: }
79:
80: static struct undo_rec *
81: new_undo_record(void)
82: {
83: struct undo_rec *rec;
84:
85: rec = LIST_FIRST(&undo_free);
1.9 vincent 86: if (rec != NULL) {
1.1 vincent 87: LIST_REMOVE(rec, next); /* Remove it from the free-list */
1.9 vincent 88: undo_free_num--;
89: } else {
1.25 db 90: if ((rec = malloc(sizeof(*rec))) == NULL)
1.1 vincent 91: panic("Out of memory in undo code (record)");
92: }
93: memset(rec, 0, sizeof(struct undo_rec));
1.9 vincent 94:
1.11 vincent 95: return (rec);
1.1 vincent 96: }
97:
1.7 vincent 98: void
1.1 vincent 99: free_undo_record(struct undo_rec *rec)
100: {
1.9 vincent 101: static int initialised = 0;
102:
103: /*
104: * On the first run, do initialisation of the free list.
105: */
106: if (initialised == 0) {
107: LIST_INIT(&undo_free);
108: initialised = 1;
109: }
1.1 vincent 110: if (rec->content != NULL) {
111: free(rec->content);
112: rec->content = NULL;
113: }
114: if (undo_free_num >= MAX_FREE_RECORDS) {
115: free(rec);
116: return;
117: }
118: undo_free_num++;
1.9 vincent 119:
1.1 vincent 120: LIST_INSERT_HEAD(&undo_free, rec, next);
121: }
122:
123: /*
124: * Drop the oldest undo record in our list. Return 1 if we could remove it,
1.25 db 125: * 0 if the undo list was empty.
1.1 vincent 126: */
127: static int
128: drop_oldest_undo_record(void)
129: {
130: struct undo_rec *rec;
131:
1.29 kjell 132: rec = LIST_END(&curbp->b_undo);
1.1 vincent 133: if (rec != NULL) {
134: undo_free_num--;
1.9 vincent 135: LIST_REMOVE(rec, next);
1.1 vincent 136: free_undo_record(rec);
1.11 vincent 137: return (1);
1.1 vincent 138: }
1.11 vincent 139: return (0);
1.1 vincent 140: }
1.9 vincent 141:
1.44 kjell 142: static int
1.22 vincent 143: lastrectype(void)
1.1 vincent 144: {
1.9 vincent 145: struct undo_rec *rec;
146:
1.29 kjell 147: if ((rec = LIST_FIRST(&curbp->b_undo)) != NULL)
1.22 vincent 148: return (rec->type);
1.11 vincent 149: return (0);
1.1 vincent 150: }
151:
1.30 kjell 152: /*
1.42 kjell 153: * Returns TRUE if undo is enabled, FALSE otherwise.
154: */
155: int
156: undo_enabled(void)
157: {
158: return (undo_enable_flag);
159: }
160:
161: /*
1.30 kjell 162: * undo_enable(TRUE/FALSE) will enable / disable the undo mechanism.
163: * Returns TRUE if previously enabled, FALSE otherwise.
164: */
1.1 vincent 165: int
166: undo_enable(int on)
167: {
1.42 kjell 168: int pon = undo_enable_flag;
1.22 vincent 169:
1.42 kjell 170: undo_enable_flag = on;
171: return (pon);
1.5 vincent 172: }
173:
1.30 kjell 174: /*
175: * If undo is enabled, then:
1.42 kjell 176: * undo_boundary_enable(FALS) stops recording undo boundaries between actions.
177: * undo_boundary_enable(TRUE) enables undo boundaries.
1.30 kjell 178: * If undo is disabled, this function has no effect.
179: */
1.42 kjell 180:
1.28 kjell 181: void
1.42 kjell 182: undo_boundary_enable(int flag)
1.28 kjell 183: {
1.42 kjell 184: if (undo_enable_flag == TRUE)
185: boundary_flag = flag;
1.28 kjell 186: }
187:
1.30 kjell 188: /*
1.42 kjell 189: * Record an undo boundary, unless boundary_flag == FALSE.
1.40 kjell 190: * Does nothing if previous undo entry is already a boundary or 'modified' flag.
1.30 kjell 191: */
1.37 kjell 192: void
1.9 vincent 193: undo_add_boundary(void)
1.5 vincent 194: {
195: struct undo_rec *rec;
1.40 kjell 196: int last;
1.5 vincent 197:
1.42 kjell 198: if (boundary_flag == FALSE)
1.37 kjell 199: return;
1.12 vincent 200:
1.40 kjell 201: last = lastrectype();
202: if (last == BOUNDARY || last == MODIFIED)
1.37 kjell 203: return;
1.31 deraadt 204:
1.5 vincent 205: rec = new_undo_record();
1.9 vincent 206: rec->type = BOUNDARY;
1.5 vincent 207:
1.29 kjell 208: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.9 vincent 209:
1.37 kjell 210: return;
1.1 vincent 211: }
212:
1.40 kjell 213: /*
214: * Record an undo "modified" boundary
215: */
216: void
217: undo_add_modified(void)
218: {
219: struct undo_rec *rec;
220:
221: rec = new_undo_record();
222: rec->type = MODIFIED;
223:
224: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
225:
226: return;
227: }
228:
1.1 vincent 229: int
1.35 deraadt 230: undo_add_insert(struct line *lp, int offset, int size)
1.1 vincent 231: {
1.35 deraadt 232: struct region reg;
1.25 db 233: struct undo_rec *rec;
234: int pos;
1.9 vincent 235:
1.42 kjell 236: if (undo_enable_flag == FALSE)
1.11 vincent 237: return (TRUE);
1.1 vincent 238: reg.r_linep = lp;
239: reg.r_offset = offset;
240: reg.r_size = size;
1.9 vincent 241:
1.24 vincent 242: pos = find_dot(lp, offset);
1.9 vincent 243:
1.1 vincent 244: /*
245: * We try to reuse the last undo record to `compress' things.
1.13 deraadt 246: */
1.29 kjell 247: rec = LIST_FIRST(&curbp->b_undo);
1.23 vincent 248: if (rec != NULL && rec->type == INSERT) {
249: if (rec->pos + rec->region.r_size == pos) {
250: rec->region.r_size += reg.r_size;
251: return (TRUE);
1.1 vincent 252: }
253: }
1.9 vincent 254:
1.1 vincent 255: /*
1.25 db 256: * We couldn't reuse the last undo record, so prepare a new one.
1.1 vincent 257: */
258: rec = new_undo_record();
1.9 vincent 259: rec->pos = pos;
1.1 vincent 260: rec->type = INSERT;
1.35 deraadt 261: memmove(&rec->region, ®, sizeof(struct region));
1.1 vincent 262: rec->content = NULL;
1.9 vincent 263:
1.27 cloder 264: undo_add_boundary();
1.9 vincent 265:
1.29 kjell 266: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 267:
1.11 vincent 268: return (TRUE);
1.1 vincent 269: }
270:
271: /*
1.25 db 272: * This of course must be done _before_ the actual deletion is done.
1.1 vincent 273: */
274: int
1.35 deraadt 275: undo_add_delete(struct line *lp, int offset, int size)
1.1 vincent 276: {
1.35 deraadt 277: struct region reg;
1.25 db 278: struct undo_rec *rec;
279: int pos;
1.1 vincent 280:
1.42 kjell 281: if (undo_enable_flag == FALSE)
1.11 vincent 282: return (TRUE);
1.1 vincent 283:
284: reg.r_linep = lp;
285: reg.r_offset = offset;
286: reg.r_size = size;
287:
1.24 vincent 288: pos = find_dot(lp, offset);
1.3 vincent 289:
1.10 vincent 290: if (offset == llength(lp)) /* if it's a newline... */
1.9 vincent 291: undo_add_boundary();
1.29 kjell 292: else if ((rec = LIST_FIRST(&curbp->b_undo)) != NULL) {
1.9 vincent 293: /*
294: * Separate this command from the previous one if we're not
295: * just before the previous record...
296: */
1.10 vincent 297: if (rec->type == DELETE) {
1.9 vincent 298: if (rec->pos - rec->region.r_size != pos)
299: undo_add_boundary();
1.23 vincent 300: }
1.3 vincent 301: }
1.1 vincent 302: rec = new_undo_record();
303: rec->pos = pos;
304:
305: rec->type = DELETE;
1.35 deraadt 306: memmove(&rec->region, ®, sizeof(struct region));
1.1 vincent 307: do {
308: rec->content = malloc(reg.r_size + 1);
309: } while ((rec->content == NULL) && drop_oldest_undo_record());
1.9 vincent 310:
1.1 vincent 311: if (rec->content == NULL)
312: panic("Out of memory");
1.9 vincent 313:
1.1 vincent 314: region_get_data(®, rec->content, reg.r_size);
315:
1.22 vincent 316: if (lastrectype() != DELETE)
317: undo_add_boundary();
318:
1.29 kjell 319: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 320:
1.11 vincent 321: return (TRUE);
1.1 vincent 322: }
323:
324: /*
1.25 db 325: * This of course must be called before the change takes place.
1.1 vincent 326: */
327: int
1.35 deraadt 328: undo_add_change(struct line *lp, int offset, int size)
1.1 vincent 329: {
1.42 kjell 330: if (undo_enable_flag == FALSE)
1.11 vincent 331: return (TRUE);
1.12 vincent 332: undo_add_boundary();
1.42 kjell 333: boundary_flag = FALSE;
1.12 vincent 334: undo_add_delete(lp, offset, size);
335: undo_add_insert(lp, offset, size);
1.42 kjell 336: boundary_flag = TRUE;
1.9 vincent 337: undo_add_boundary();
338:
1.11 vincent 339: return (TRUE);
1.8 vincent 340: }
341:
342: /*
343: * Show the undo records for the current buffer in a new buffer.
344: */
1.32 kjell 345: /* ARGSUSED */
1.8 vincent 346: int
1.18 vincent 347: undo_dump(int f, int n)
1.8 vincent 348: {
1.25 db 349: struct undo_rec *rec;
1.35 deraadt 350: struct buffer *bp;
351: struct mgwin *wp;
1.25 db 352: char buf[4096], tmp[1024];
353: int num;
1.8 vincent 354:
355: /*
356: * Prepare the buffer for insertion.
357: */
358: if ((bp = bfind("*undo*", TRUE)) == NULL)
1.11 vincent 359: return (FALSE);
1.9 vincent 360: bp->b_flag |= BFREADONLY;
1.8 vincent 361: bclear(bp);
362: popbuf(bp);
363:
1.10 vincent 364: for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
1.8 vincent 365: if (wp->w_bufp == bp) {
1.41 kjell 366: wp->w_dotp = bp->b_headp;
1.8 vincent 367: wp->w_doto = 0;
368: }
1.10 vincent 369: }
1.8 vincent 370:
371: num = 0;
1.29 kjell 372: for (rec = LIST_FIRST(&curbp->b_undo); rec != NULL;
1.13 deraadt 373: rec = LIST_NEXT(rec, next)) {
1.8 vincent 374: num++;
1.25 db 375: snprintf(buf, sizeof(buf),
1.38 kjell 376: "%d:\t %s at %d ", num,
1.8 vincent 377: (rec->type == DELETE) ? "DELETE":
378: (rec->type == INSERT) ? "INSERT":
1.40 kjell 379: (rec->type == BOUNDARY) ? "----" :
380: (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN",
1.8 vincent 381: rec->pos);
1.10 vincent 382:
1.12 vincent 383: if (rec->content) {
1.38 kjell 384: (void)strlcat(buf, "\"", sizeof(buf));
1.25 db 385: snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
1.8 vincent 386: rec->content);
1.38 kjell 387: (void)strlcat(buf, tmp, sizeof(buf));
388: (void)strlcat(buf, "\"", sizeof(buf));
1.8 vincent 389: }
1.25 db 390: snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
1.33 kjell 391: if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) {
392: ewprintf("Undo record too large. Aborted.");
393: return (FALSE);
394: }
1.17 deraadt 395: addlinef(bp, "%s", buf);
1.8 vincent 396: }
1.11 vincent 397: return (TRUE);
1.1 vincent 398: }
399:
1.9 vincent 400: /*
1.25 db 401: * After the user did action1, then action2, then action3:
1.9 vincent 402: *
403: * [action3] <--- Undoptr
404: * [action2]
405: * [action1]
406: * ------
407: * [undo]
408: *
409: * After undo:
410: *
411: * [undo of action3]
412: * [action2] <--- Undoptr
413: * [action1]
414: * ------
415: * [undo]
1.13 deraadt 416: *
1.9 vincent 417: * After another undo:
418: *
419: *
420: * [undo of action2]
421: * [undo of action3]
422: * [action1] <--- Undoptr
423: * ------
424: * [undo]
1.13 deraadt 425: *
1.25 db 426: * Note that the "undo of actionX" have no special meaning. Only when
427: * we undo a deletion, the insertion will be recorded just as if it
1.10 vincent 428: * was typed on the keyboard. Resulting in the inverse operation being
1.9 vincent 429: * saved in the list.
430: *
431: * If undoptr reaches the bottom of the list, or if we moved between
432: * two undo actions, we make it point back at the topmost record. This is
433: * how we handle redoing.
434: */
1.32 kjell 435: /* ARGSUSED */
1.1 vincent 436: int
1.4 vincent 437: undo(int f, int n)
1.1 vincent 438: {
1.25 db 439: struct undo_rec *ptr, *nptr;
1.31 deraadt 440: int done, rval;
1.36 kjell 441: struct line *lp;
1.25 db 442: int offset, save, dot;
1.28 kjell 443: static int nulled = FALSE;
1.39 kjell 444: int lineno;
1.23 vincent 445:
1.24 vincent 446: dot = find_dot(curwp->w_dotp, curwp->w_doto);
1.9 vincent 447:
1.29 kjell 448: ptr = curbp->b_undoptr;
1.9 vincent 449:
450: /* if we moved, make ptr point back to the top of the list */
1.29 kjell 451: if ((ptr == NULL && nulled == TRUE) || curbp->b_undopos != dot) {
452: ptr = LIST_FIRST(&curbp->b_undo);
1.28 kjell 453: nulled = TRUE;
454: }
1.1 vincent 455:
1.9 vincent 456: rval = TRUE;
1.10 vincent 457: while (n--) {
1.9 vincent 458: /* if we have a spurious boundary, free it and move on.... */
459: while (ptr && ptr->type == BOUNDARY) {
460: nptr = LIST_NEXT(ptr, next);
461: LIST_REMOVE(ptr, next);
462: free_undo_record(ptr);
463: ptr = nptr;
1.4 vincent 464: }
465: /*
1.9 vincent 466: * Ptr is NULL, but on the next run, it will point to the
467: * top again, redoing all stuff done in the buffer since
468: * its creation.
1.4 vincent 469: */
1.9 vincent 470: if (ptr == NULL) {
1.11 vincent 471: ewprintf("No further undo information");
1.9 vincent 472: rval = FALSE;
1.28 kjell 473: nulled = TRUE;
1.4 vincent 474: break;
475: }
1.28 kjell 476: nulled = FALSE;
1.6 vincent 477:
1.13 deraadt 478: /*
1.9 vincent 479: * Loop while we don't get a boundary specifying we've
480: * finished the current action...
481: */
1.22 vincent 482:
1.28 kjell 483: undo_add_boundary();
1.22 vincent 484:
1.42 kjell 485: save = boundary_flag;
486: boundary_flag = FALSE;
1.22 vincent 487:
1.9 vincent 488: done = 0;
489: do {
490: /*
491: * Move to where this has to apply
492: *
1.40 kjell 493: * Boundaries (and the modified flag) are put as
494: * position 0 (to save lookup time in find_dot)
495: * so we must not move there...
1.9 vincent 496: */
1.40 kjell 497: if (ptr->type != BOUNDARY && ptr->type != MODIFIED) {
1.24 vincent 498: if (find_lo(ptr->pos, &lp,
1.39 kjell 499: &offset, &lineno) == FALSE) {
1.9 vincent 500: ewprintf("Internal error in Undo!");
501: rval = FALSE;
502: break;
503: }
504: curwp->w_dotp = lp;
505: curwp->w_doto = offset;
1.39 kjell 506: curwp->w_markline = curwp->w_dotline;
507: curwp->w_dotline = lineno;
1.9 vincent 508: }
509:
510: /*
511: * Do operation^-1
512: */
513: switch (ptr->type) {
514: case INSERT:
1.34 kjell 515: ldelete(ptr->region.r_size, KNONE);
1.9 vincent 516: break;
517: case DELETE:
1.13 deraadt 518: region_put_data(ptr->content,
1.9 vincent 519: ptr->region.r_size);
520: break;
521: case BOUNDARY:
522: done = 1;
1.40 kjell 523: break;
524: case MODIFIED:
525: curbp->b_flag &= ~BFCHG;
1.9 vincent 526: break;
527: default:
528: break;
529: }
530:
531: /* And move to next record */
1.22 vincent 532: ptr = LIST_NEXT(ptr, next);
1.9 vincent 533: } while (ptr != NULL && !done);
1.22 vincent 534:
1.42 kjell 535: boundary_flag = save;
1.22 vincent 536: undo_add_boundary();
1.9 vincent 537:
538: ewprintf("Undo!");
1.1 vincent 539: }
1.9 vincent 540: /*
541: * Record where we are. (we have to save our new position at the end
542: * since we change the dot when undoing....)
543: */
1.29 kjell 544: curbp->b_undoptr = ptr;
1.23 vincent 545:
1.29 kjell 546: curbp->b_undopos = find_dot(curwp->w_dotp, curwp->w_doto);
1.9 vincent 547:
1.13 deraadt 548: return (rval);
1.1 vincent 549: }