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