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