Annotation of src/usr.bin/mg/undo.c, Revision 1.54
1.54 ! lum 1: /* $OpenBSD: undo.c,v 1.53 2013/03/25 11:39:38 florian 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.50 oga 14: static struct undoq undo_free;
1.7 vincent 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:
1.50 oga 85: rec = TAILQ_FIRST(&undo_free);
1.9 vincent 86: if (rec != NULL) {
1.50 oga 87: /* Remove it from the free-list */
88: TAILQ_REMOVE(&undo_free, rec, next);
1.9 vincent 89: undo_free_num--;
90: } else {
1.25 db 91: if ((rec = malloc(sizeof(*rec))) == NULL)
1.1 vincent 92: panic("Out of memory in undo code (record)");
93: }
94: memset(rec, 0, sizeof(struct undo_rec));
1.9 vincent 95:
1.11 vincent 96: return (rec);
1.1 vincent 97: }
98:
1.7 vincent 99: void
1.1 vincent 100: free_undo_record(struct undo_rec *rec)
101: {
1.9 vincent 102: static int initialised = 0;
103:
104: /*
105: * On the first run, do initialisation of the free list.
106: */
107: if (initialised == 0) {
1.50 oga 108: TAILQ_INIT(&undo_free);
1.9 vincent 109: initialised = 1;
110: }
1.1 vincent 111: if (rec->content != NULL) {
112: free(rec->content);
113: rec->content = NULL;
114: }
115: if (undo_free_num >= MAX_FREE_RECORDS) {
116: free(rec);
117: return;
118: }
119: undo_free_num++;
1.9 vincent 120:
1.50 oga 121: TAILQ_INSERT_HEAD(&undo_free, rec, next);
1.1 vincent 122: }
123:
124: /*
125: * Drop the oldest undo record in our list. Return 1 if we could remove it,
1.25 db 126: * 0 if the undo list was empty.
1.1 vincent 127: */
128: static int
129: drop_oldest_undo_record(void)
130: {
131: struct undo_rec *rec;
132:
1.50 oga 133: rec = TAILQ_LAST(&curbp->b_undo, undoq);
1.1 vincent 134: if (rec != NULL) {
135: undo_free_num--;
1.50 oga 136: TAILQ_REMOVE(&curbp->b_undo, rec, next);
1.1 vincent 137: free_undo_record(rec);
1.11 vincent 138: return (1);
1.1 vincent 139: }
1.11 vincent 140: return (0);
1.1 vincent 141: }
1.9 vincent 142:
1.44 kjell 143: static int
1.22 vincent 144: lastrectype(void)
1.1 vincent 145: {
1.9 vincent 146: struct undo_rec *rec;
147:
1.50 oga 148: if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL)
1.22 vincent 149: return (rec->type);
1.11 vincent 150: return (0);
1.1 vincent 151: }
152:
1.30 kjell 153: /*
1.42 kjell 154: * Returns TRUE if undo is enabled, FALSE otherwise.
155: */
156: int
157: undo_enabled(void)
158: {
159: return (undo_enable_flag);
160: }
161:
162: /*
1.47 kjell 163: * undo_enable: toggle undo_enable.
164: * Returns the previous value of the flag.
1.30 kjell 165: */
1.1 vincent 166: int
1.47 kjell 167: undo_enable(int f, int n)
1.1 vincent 168: {
1.42 kjell 169: int pon = undo_enable_flag;
1.22 vincent 170:
1.47 kjell 171: if (f & (FFARG | FFRAND))
172: undo_enable_flag = n > 0;
173: else
174: undo_enable_flag = !undo_enable_flag;
175:
176: if (!(f & FFRAND))
177: ewprintf("Undo %sabled", undo_enable_flag ? "en" : "dis");
178:
1.42 kjell 179: return (pon);
1.5 vincent 180: }
181:
1.30 kjell 182: /*
183: * If undo is enabled, then:
1.47 kjell 184: * Toggle undo boundary recording.
185: * If called with an argument, (n > 0) => enable. Otherwise disable.
186: * In either case, add an undo boundary
1.30 kjell 187: * If undo is disabled, this function has no effect.
188: */
1.47 kjell 189: int
190: undo_boundary_enable(int f, int n)
191: {
192: int bon = boundary_flag;
1.42 kjell 193:
1.47 kjell 194: if (!undo_enable_flag)
195: return (FALSE);
196:
197: undo_add_boundary(FFRAND, 1);
198:
199: if (f & (FFARG | FFRAND))
200: boundary_flag = n > 0;
201: else
202: boundary_flag = !boundary_flag;
203:
204: if (!(f & FFRAND))
205: ewprintf("Undo boundaries %sabled",
206: boundary_flag ? "en" : "dis");
207:
208: return (bon);
1.28 kjell 209: }
210:
1.30 kjell 211: /*
1.42 kjell 212: * Record an undo boundary, unless boundary_flag == FALSE.
1.40 kjell 213: * Does nothing if previous undo entry is already a boundary or 'modified' flag.
1.30 kjell 214: */
1.47 kjell 215: int
216: undo_add_boundary(int f, int n)
1.5 vincent 217: {
218: struct undo_rec *rec;
1.40 kjell 219: int last;
1.5 vincent 220:
1.42 kjell 221: if (boundary_flag == FALSE)
1.47 kjell 222: return (FALSE);
1.12 vincent 223:
1.40 kjell 224: last = lastrectype();
225: if (last == BOUNDARY || last == MODIFIED)
1.47 kjell 226: return (TRUE);
1.31 deraadt 227:
1.5 vincent 228: rec = new_undo_record();
1.9 vincent 229: rec->type = BOUNDARY;
1.5 vincent 230:
1.50 oga 231: TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
1.9 vincent 232:
1.47 kjell 233: return (TRUE);
1.1 vincent 234: }
235:
1.40 kjell 236: /*
237: * Record an undo "modified" boundary
238: */
239: void
240: undo_add_modified(void)
241: {
1.52 florian 242: struct undo_rec *rec, *trec;
243:
244: TAILQ_FOREACH_SAFE(rec, &curbp->b_undo, next, trec)
245: if (rec->type == MODIFIED) {
246: TAILQ_REMOVE(&curbp->b_undo, rec, next);
247: free_undo_record(rec);
248: }
1.40 kjell 249:
250: rec = new_undo_record();
251: rec->type = MODIFIED;
252:
1.50 oga 253: TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
1.40 kjell 254:
255: return;
256: }
257:
1.1 vincent 258: int
1.35 deraadt 259: undo_add_insert(struct line *lp, int offset, int size)
1.1 vincent 260: {
1.35 deraadt 261: struct region reg;
1.25 db 262: struct undo_rec *rec;
263: int pos;
1.9 vincent 264:
1.47 kjell 265: if (!undo_enable_flag)
1.11 vincent 266: return (TRUE);
1.1 vincent 267: reg.r_linep = lp;
268: reg.r_offset = offset;
269: reg.r_size = size;
1.9 vincent 270:
1.24 vincent 271: pos = find_dot(lp, offset);
1.9 vincent 272:
1.1 vincent 273: /*
274: * We try to reuse the last undo record to `compress' things.
1.13 deraadt 275: */
1.50 oga 276: rec = TAILQ_FIRST(&curbp->b_undo);
1.23 vincent 277: if (rec != NULL && rec->type == INSERT) {
278: if (rec->pos + rec->region.r_size == pos) {
279: rec->region.r_size += reg.r_size;
280: return (TRUE);
1.1 vincent 281: }
282: }
1.9 vincent 283:
1.1 vincent 284: /*
1.25 db 285: * We couldn't reuse the last undo record, so prepare a new one.
1.1 vincent 286: */
287: rec = new_undo_record();
1.9 vincent 288: rec->pos = pos;
1.1 vincent 289: rec->type = INSERT;
1.35 deraadt 290: memmove(&rec->region, ®, sizeof(struct region));
1.1 vincent 291: rec->content = NULL;
1.9 vincent 292:
1.47 kjell 293: undo_add_boundary(FFRAND, 1);
1.9 vincent 294:
1.50 oga 295: TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 296:
1.11 vincent 297: return (TRUE);
1.1 vincent 298: }
299:
300: /*
1.25 db 301: * This of course must be done _before_ the actual deletion is done.
1.1 vincent 302: */
303: int
1.49 kjell 304: undo_add_delete(struct line *lp, int offset, int size, int isreg)
1.1 vincent 305: {
1.35 deraadt 306: struct region reg;
1.25 db 307: struct undo_rec *rec;
308: int pos;
1.1 vincent 309:
1.47 kjell 310: if (!undo_enable_flag)
1.11 vincent 311: return (TRUE);
1.1 vincent 312:
313: reg.r_linep = lp;
314: reg.r_offset = offset;
315: reg.r_size = size;
316:
1.24 vincent 317: pos = find_dot(lp, offset);
1.3 vincent 318:
1.10 vincent 319: if (offset == llength(lp)) /* if it's a newline... */
1.47 kjell 320: undo_add_boundary(FFRAND, 1);
1.50 oga 321: else if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) {
1.9 vincent 322: /*
323: * Separate this command from the previous one if we're not
324: * just before the previous record...
325: */
1.49 kjell 326: if (!isreg && rec->type == DELETE) {
1.9 vincent 327: if (rec->pos - rec->region.r_size != pos)
1.47 kjell 328: undo_add_boundary(FFRAND, 1);
1.23 vincent 329: }
1.3 vincent 330: }
1.1 vincent 331: rec = new_undo_record();
332: rec->pos = pos;
1.49 kjell 333: if (isreg)
334: rec->type = DELREG;
335: else
336: rec->type = DELETE;
1.35 deraadt 337: memmove(&rec->region, ®, sizeof(struct region));
1.1 vincent 338: do {
339: rec->content = malloc(reg.r_size + 1);
340: } while ((rec->content == NULL) && drop_oldest_undo_record());
1.9 vincent 341:
1.1 vincent 342: if (rec->content == NULL)
343: panic("Out of memory");
1.9 vincent 344:
1.1 vincent 345: region_get_data(®, rec->content, reg.r_size);
346:
1.49 kjell 347: if (isreg || lastrectype() != DELETE)
1.47 kjell 348: undo_add_boundary(FFRAND, 1);
1.22 vincent 349:
1.50 oga 350: TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 351:
1.11 vincent 352: return (TRUE);
1.1 vincent 353: }
354:
355: /*
1.25 db 356: * This of course must be called before the change takes place.
1.1 vincent 357: */
358: int
1.35 deraadt 359: undo_add_change(struct line *lp, int offset, int size)
1.1 vincent 360: {
1.47 kjell 361: if (!undo_enable_flag)
1.11 vincent 362: return (TRUE);
1.47 kjell 363: undo_add_boundary(FFRAND, 1);
1.42 kjell 364: boundary_flag = FALSE;
1.49 kjell 365: undo_add_delete(lp, offset, size, 0);
1.12 vincent 366: undo_add_insert(lp, offset, size);
1.42 kjell 367: boundary_flag = TRUE;
1.47 kjell 368: undo_add_boundary(FFRAND, 1);
1.9 vincent 369:
1.11 vincent 370: return (TRUE);
1.8 vincent 371: }
372:
373: /*
374: * Show the undo records for the current buffer in a new buffer.
375: */
1.32 kjell 376: /* ARGSUSED */
1.8 vincent 377: int
1.18 vincent 378: undo_dump(int f, int n)
1.8 vincent 379: {
1.25 db 380: struct undo_rec *rec;
1.35 deraadt 381: struct buffer *bp;
382: struct mgwin *wp;
1.25 db 383: char buf[4096], tmp[1024];
384: int num;
1.8 vincent 385:
386: /*
387: * Prepare the buffer for insertion.
388: */
389: if ((bp = bfind("*undo*", TRUE)) == NULL)
1.11 vincent 390: return (FALSE);
1.9 vincent 391: bp->b_flag |= BFREADONLY;
1.8 vincent 392: bclear(bp);
1.54 ! lum 393: if ((wp = popbuf(bp, WNONE)) == NULL)
! 394: return (FALSE);
1.8 vincent 395:
1.10 vincent 396: for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
1.8 vincent 397: if (wp->w_bufp == bp) {
1.41 kjell 398: wp->w_dotp = bp->b_headp;
1.8 vincent 399: wp->w_doto = 0;
400: }
1.10 vincent 401: }
1.8 vincent 402:
403: num = 0;
1.50 oga 404: TAILQ_FOREACH(rec, &curbp->b_undo, next) {
1.8 vincent 405: num++;
1.25 db 406: snprintf(buf, sizeof(buf),
1.38 kjell 407: "%d:\t %s at %d ", num,
1.8 vincent 408: (rec->type == DELETE) ? "DELETE":
1.49 kjell 409: (rec->type == DELREG) ? "DELREGION":
1.8 vincent 410: (rec->type == INSERT) ? "INSERT":
1.40 kjell 411: (rec->type == BOUNDARY) ? "----" :
412: (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN",
1.8 vincent 413: rec->pos);
1.10 vincent 414:
1.12 vincent 415: if (rec->content) {
1.38 kjell 416: (void)strlcat(buf, "\"", sizeof(buf));
1.25 db 417: snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
1.8 vincent 418: rec->content);
1.38 kjell 419: (void)strlcat(buf, tmp, sizeof(buf));
420: (void)strlcat(buf, "\"", sizeof(buf));
1.8 vincent 421: }
1.25 db 422: snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
1.33 kjell 423: if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) {
424: ewprintf("Undo record too large. Aborted.");
425: return (FALSE);
426: }
1.17 deraadt 427: addlinef(bp, "%s", buf);
1.53 florian 428: }
429: for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
430: if (wp->w_bufp == bp) {
431: wp->w_dotline = num+1;
432: wp->w_rflag |= WFFULL;
433: }
1.8 vincent 434: }
1.11 vincent 435: return (TRUE);
1.1 vincent 436: }
437:
1.9 vincent 438: /*
1.25 db 439: * After the user did action1, then action2, then action3:
1.9 vincent 440: *
441: * [action3] <--- Undoptr
442: * [action2]
443: * [action1]
444: * ------
445: * [undo]
446: *
447: * After undo:
448: *
449: * [undo of action3]
450: * [action2] <--- Undoptr
451: * [action1]
452: * ------
453: * [undo]
1.13 deraadt 454: *
1.9 vincent 455: * After another undo:
456: *
457: *
458: * [undo of action2]
459: * [undo of action3]
460: * [action1] <--- Undoptr
461: * ------
462: * [undo]
1.13 deraadt 463: *
1.25 db 464: * Note that the "undo of actionX" have no special meaning. Only when
465: * we undo a deletion, the insertion will be recorded just as if it
1.10 vincent 466: * was typed on the keyboard. Resulting in the inverse operation being
1.9 vincent 467: * saved in the list.
468: *
469: * If undoptr reaches the bottom of the list, or if we moved between
470: * two undo actions, we make it point back at the topmost record. This is
471: * how we handle redoing.
472: */
1.32 kjell 473: /* ARGSUSED */
1.1 vincent 474: int
1.4 vincent 475: undo(int f, int n)
1.1 vincent 476: {
1.25 db 477: struct undo_rec *ptr, *nptr;
1.31 deraadt 478: int done, rval;
1.36 kjell 479: struct line *lp;
1.51 florian 480: int offset, save;
1.28 kjell 481: static int nulled = FALSE;
1.39 kjell 482: int lineno;
1.23 vincent 483:
1.46 kjell 484: if (n < 0)
485: return (FALSE);
486:
1.29 kjell 487: ptr = curbp->b_undoptr;
1.9 vincent 488:
1.46 kjell 489: /* first invocation, make ptr point back to the top of the list */
490: if ((ptr == NULL && nulled == TRUE) || rptcount == 0) {
1.50 oga 491: ptr = TAILQ_FIRST(&curbp->b_undo);
1.28 kjell 492: nulled = TRUE;
493: }
1.1 vincent 494:
1.9 vincent 495: rval = TRUE;
1.10 vincent 496: while (n--) {
1.9 vincent 497: /* if we have a spurious boundary, free it and move on.... */
498: while (ptr && ptr->type == BOUNDARY) {
1.50 oga 499: nptr = TAILQ_NEXT(ptr, next);
500: TAILQ_REMOVE(&curbp->b_undo, ptr, next);
1.9 vincent 501: free_undo_record(ptr);
502: ptr = nptr;
1.4 vincent 503: }
504: /*
1.9 vincent 505: * Ptr is NULL, but on the next run, it will point to the
506: * top again, redoing all stuff done in the buffer since
507: * its creation.
1.4 vincent 508: */
1.9 vincent 509: if (ptr == NULL) {
1.11 vincent 510: ewprintf("No further undo information");
1.9 vincent 511: rval = FALSE;
1.28 kjell 512: nulled = TRUE;
1.4 vincent 513: break;
514: }
1.28 kjell 515: nulled = FALSE;
1.6 vincent 516:
1.13 deraadt 517: /*
1.9 vincent 518: * Loop while we don't get a boundary specifying we've
519: * finished the current action...
520: */
1.22 vincent 521:
1.47 kjell 522: undo_add_boundary(FFRAND, 1);
1.22 vincent 523:
1.42 kjell 524: save = boundary_flag;
525: boundary_flag = FALSE;
1.22 vincent 526:
1.9 vincent 527: done = 0;
528: do {
529: /*
530: * Move to where this has to apply
531: *
1.40 kjell 532: * Boundaries (and the modified flag) are put as
533: * position 0 (to save lookup time in find_dot)
534: * so we must not move there...
1.9 vincent 535: */
1.40 kjell 536: if (ptr->type != BOUNDARY && ptr->type != MODIFIED) {
1.24 vincent 537: if (find_lo(ptr->pos, &lp,
1.39 kjell 538: &offset, &lineno) == FALSE) {
1.9 vincent 539: ewprintf("Internal error in Undo!");
540: rval = FALSE;
541: break;
542: }
543: curwp->w_dotp = lp;
544: curwp->w_doto = offset;
1.39 kjell 545: curwp->w_markline = curwp->w_dotline;
546: curwp->w_dotline = lineno;
1.9 vincent 547: }
548:
549: /*
550: * Do operation^-1
551: */
552: switch (ptr->type) {
553: case INSERT:
1.34 kjell 554: ldelete(ptr->region.r_size, KNONE);
1.9 vincent 555: break;
556: case DELETE:
1.46 kjell 557: lp = curwp->w_dotp;
558: offset = curwp->w_doto;
1.13 deraadt 559: region_put_data(ptr->content,
1.9 vincent 560: ptr->region.r_size);
1.46 kjell 561: curwp->w_dotp = lp;
562: curwp->w_doto = offset;
1.49 kjell 563: break;
564: case DELREG:
565: region_put_data(ptr->content,
566: ptr->region.r_size);
1.9 vincent 567: break;
568: case BOUNDARY:
569: done = 1;
1.40 kjell 570: break;
571: case MODIFIED:
572: curbp->b_flag &= ~BFCHG;
1.9 vincent 573: break;
574: default:
575: break;
576: }
577:
578: /* And move to next record */
1.50 oga 579: ptr = TAILQ_NEXT(ptr, next);
1.9 vincent 580: } while (ptr != NULL && !done);
1.22 vincent 581:
1.42 kjell 582: boundary_flag = save;
1.47 kjell 583: undo_add_boundary(FFRAND, 1);
1.9 vincent 584:
585: ewprintf("Undo!");
1.1 vincent 586: }
1.51 florian 587:
1.29 kjell 588: curbp->b_undoptr = ptr;
1.9 vincent 589:
1.13 deraadt 590: return (rval);
1.1 vincent 591: }