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