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