Annotation of src/usr.bin/mg/undo.c, Revision 1.28
1.28 ! kjell 1: /* $OpenBSD: undo.c,v 1.27 2005/05/29 21:37:49 cloder 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.19 vincent 158: rec = LIST_END(&curwp->w_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.22 vincent 173: if ((rec = LIST_FIRST(&curwp->w_undo)) != NULL)
174: return (rec->type);
1.11 vincent 175: return (0);
1.1 vincent 176: }
177:
178: int
179: undo_enable(int on)
180: {
1.14 vincent 181: int pon = undo_disable_flag;
1.22 vincent 182:
1.14 vincent 183: undo_disable_flag = (on == TRUE) ? 0 : 1;
184: return (pon ? FALSE : TRUE);
1.5 vincent 185: }
186:
1.28 ! kjell 187: void
! 188: undo_no_boundary(int flag)
! 189: {
! 190: if (!undo_disable_flag)
! 191: nobound = flag;
! 192: }
! 193:
1.5 vincent 194: int
1.9 vincent 195: undo_add_boundary(void)
1.5 vincent 196: {
197: struct undo_rec *rec;
198:
1.12 vincent 199: if (nobound)
200: return (TRUE);
201:
1.28 ! kjell 202: if (lastrectype() == BOUNDARY)
! 203: return (TRUE);
! 204:
1.5 vincent 205: rec = new_undo_record();
1.9 vincent 206: rec->type = BOUNDARY;
1.5 vincent 207:
1.19 vincent 208: LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.9 vincent 209:
1.11 vincent 210: return (TRUE);
1.1 vincent 211: }
212:
213: int
214: undo_add_insert(LINE *lp, int offset, int size)
215: {
1.25 db 216: REGION reg;
217: struct undo_rec *rec;
218: int pos;
1.9 vincent 219:
1.1 vincent 220: if (undo_disable_flag)
1.11 vincent 221: return (TRUE);
1.1 vincent 222: reg.r_linep = lp;
223: reg.r_offset = offset;
224: reg.r_size = size;
1.9 vincent 225:
1.24 vincent 226: pos = find_dot(lp, offset);
1.9 vincent 227:
1.1 vincent 228: /*
229: * We try to reuse the last undo record to `compress' things.
1.13 deraadt 230: */
1.19 vincent 231: rec = LIST_FIRST(&curwp->w_undo);
1.23 vincent 232: if (rec != NULL && rec->type == INSERT) {
233: if (rec->pos + rec->region.r_size == pos) {
234: rec->region.r_size += reg.r_size;
235: return (TRUE);
1.1 vincent 236: }
237: }
1.9 vincent 238:
1.1 vincent 239: /*
1.25 db 240: * We couldn't reuse the last undo record, so prepare a new one.
1.1 vincent 241: */
242: rec = new_undo_record();
1.9 vincent 243: rec->pos = pos;
1.1 vincent 244: rec->type = INSERT;
245: memmove(&rec->region, ®, sizeof(REGION));
246: rec->content = NULL;
1.9 vincent 247:
1.27 cloder 248: undo_add_boundary();
1.9 vincent 249:
1.19 vincent 250: LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.1 vincent 251:
1.11 vincent 252: return (TRUE);
1.1 vincent 253: }
254:
255: /*
1.25 db 256: * This of course must be done _before_ the actual deletion is done.
1.1 vincent 257: */
258: int
259: undo_add_delete(LINE *lp, int offset, int size)
260: {
1.25 db 261: REGION reg;
262: struct undo_rec *rec;
263: int pos;
1.1 vincent 264:
265: if (undo_disable_flag)
1.11 vincent 266: return (TRUE);
1.1 vincent 267:
268: reg.r_linep = lp;
269: reg.r_offset = offset;
270: reg.r_size = size;
271:
1.24 vincent 272: pos = find_dot(lp, offset);
1.3 vincent 273:
1.10 vincent 274: if (offset == llength(lp)) /* if it's a newline... */
1.9 vincent 275: undo_add_boundary();
1.19 vincent 276: else if ((rec = LIST_FIRST(&curwp->w_undo)) != NULL) {
1.9 vincent 277: /*
278: * Separate this command from the previous one if we're not
279: * just before the previous record...
280: */
1.10 vincent 281: if (rec->type == DELETE) {
1.9 vincent 282: if (rec->pos - rec->region.r_size != pos)
283: undo_add_boundary();
1.23 vincent 284: }
1.3 vincent 285: }
1.1 vincent 286: rec = new_undo_record();
287: rec->pos = pos;
288:
289: rec->type = DELETE;
290: memmove(&rec->region, ®, sizeof(REGION));
291: do {
292: rec->content = malloc(reg.r_size + 1);
293: } while ((rec->content == NULL) && drop_oldest_undo_record());
1.9 vincent 294:
1.1 vincent 295: if (rec->content == NULL)
296: panic("Out of memory");
1.9 vincent 297:
1.1 vincent 298: region_get_data(®, rec->content, reg.r_size);
299:
1.22 vincent 300: if (lastrectype() != DELETE)
301: undo_add_boundary();
302:
1.19 vincent 303: LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.1 vincent 304:
1.11 vincent 305: return (TRUE);
1.1 vincent 306: }
307:
308: /*
1.25 db 309: * This of course must be called before the change takes place.
1.1 vincent 310: */
311: int
312: undo_add_change(LINE *lp, int offset, int size)
313: {
314: if (undo_disable_flag)
1.11 vincent 315: return (TRUE);
1.12 vincent 316: undo_add_boundary();
1.28 ! kjell 317: nobound = TRUE;
1.12 vincent 318: undo_add_delete(lp, offset, size);
319: undo_add_insert(lp, offset, size);
1.28 ! kjell 320: nobound = FALSE;
1.9 vincent 321: undo_add_boundary();
322:
1.11 vincent 323: return (TRUE);
1.8 vincent 324: }
325:
326: /*
327: * Show the undo records for the current buffer in a new buffer.
328: */
329: int
1.18 vincent 330: undo_dump(int f, int n)
1.8 vincent 331: {
1.25 db 332: struct undo_rec *rec;
333: BUFFER *bp;
334: MGWIN *wp;
335: char buf[4096], tmp[1024];
336: int num;
1.8 vincent 337:
338: /*
339: * Prepare the buffer for insertion.
340: */
341: if ((bp = bfind("*undo*", TRUE)) == NULL)
1.11 vincent 342: return (FALSE);
1.9 vincent 343: bp->b_flag |= BFREADONLY;
1.8 vincent 344: bclear(bp);
345: popbuf(bp);
346:
1.10 vincent 347: for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
1.8 vincent 348: if (wp->w_bufp == bp) {
349: wp->w_dotp = bp->b_linep;
350: wp->w_doto = 0;
351: }
1.10 vincent 352: }
1.8 vincent 353:
354: num = 0;
1.19 vincent 355: for (rec = LIST_FIRST(&curwp->w_undo); rec != NULL;
1.13 deraadt 356: rec = LIST_NEXT(rec, next)) {
1.8 vincent 357: num++;
1.25 db 358: snprintf(buf, sizeof(buf),
1.8 vincent 359: "Record %d =>\t %s at %d ", num,
360: (rec->type == DELETE) ? "DELETE":
361: (rec->type == INSERT) ? "INSERT":
362: (rec->type == BOUNDARY) ? "----" : "UNKNOWN",
363: rec->pos);
1.10 vincent 364:
1.12 vincent 365: if (rec->content) {
1.25 db 366: strlcat(buf, "\"", sizeof(buf));
367: snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
1.8 vincent 368: rec->content);
1.25 db 369: strlcat(buf, tmp, sizeof(buf));
370: strlcat(buf, "\"", sizeof(buf));
1.8 vincent 371: }
1.25 db 372: snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
373: strlcat(buf, tmp, sizeof(buf));
1.17 deraadt 374: addlinef(bp, "%s", buf);
1.8 vincent 375: }
1.11 vincent 376: return (TRUE);
1.1 vincent 377: }
378:
1.9 vincent 379: /*
1.25 db 380: * After the user did action1, then action2, then action3:
1.9 vincent 381: *
382: * [action3] <--- Undoptr
383: * [action2]
384: * [action1]
385: * ------
386: * [undo]
387: *
388: * After undo:
389: *
390: * [undo of action3]
391: * [action2] <--- Undoptr
392: * [action1]
393: * ------
394: * [undo]
1.13 deraadt 395: *
1.9 vincent 396: * After another undo:
397: *
398: *
399: * [undo of action2]
400: * [undo of action3]
401: * [action1] <--- Undoptr
402: * ------
403: * [undo]
1.13 deraadt 404: *
1.25 db 405: * Note that the "undo of actionX" have no special meaning. Only when
406: * we undo a deletion, the insertion will be recorded just as if it
1.10 vincent 407: * was typed on the keyboard. Resulting in the inverse operation being
1.9 vincent 408: * saved in the list.
409: *
410: * If undoptr reaches the bottom of the list, or if we moved between
411: * two undo actions, we make it point back at the topmost record. This is
412: * how we handle redoing.
413: */
1.1 vincent 414: int
1.4 vincent 415: undo(int f, int n)
1.1 vincent 416: {
1.25 db 417: struct undo_rec *ptr, *nptr;
418: int done, rval;
419: LINE *lp;
420: int offset, save, dot;
1.28 ! kjell 421: static int nulled = FALSE;
1.23 vincent 422:
1.24 vincent 423: dot = find_dot(curwp->w_dotp, curwp->w_doto);
1.9 vincent 424:
1.19 vincent 425: ptr = curwp->w_undoptr;
1.9 vincent 426:
427: /* if we moved, make ptr point back to the top of the list */
1.28 ! kjell 428: if ((ptr == NULL && nulled == TRUE) || curwp->w_undopos != dot) {
1.19 vincent 429: ptr = LIST_FIRST(&curwp->w_undo);
1.28 ! kjell 430: nulled = TRUE;
! 431: }
1.1 vincent 432:
1.9 vincent 433: rval = TRUE;
1.10 vincent 434: while (n--) {
1.9 vincent 435: /* if we have a spurious boundary, free it and move on.... */
436: while (ptr && ptr->type == BOUNDARY) {
437: nptr = LIST_NEXT(ptr, next);
438: LIST_REMOVE(ptr, next);
439: free_undo_record(ptr);
440: ptr = nptr;
1.4 vincent 441: }
442: /*
1.9 vincent 443: * Ptr is NULL, but on the next run, it will point to the
444: * top again, redoing all stuff done in the buffer since
445: * its creation.
1.4 vincent 446: */
1.9 vincent 447: if (ptr == NULL) {
1.11 vincent 448: ewprintf("No further undo information");
1.9 vincent 449: rval = FALSE;
1.28 ! kjell 450: nulled = TRUE;
1.4 vincent 451: break;
452: }
1.28 ! kjell 453: nulled = FALSE;
1.6 vincent 454:
1.13 deraadt 455: /*
1.9 vincent 456: * Loop while we don't get a boundary specifying we've
457: * finished the current action...
458: */
1.22 vincent 459:
1.28 ! kjell 460: undo_add_boundary();
1.22 vincent 461:
462: save = nobound;
1.28 ! kjell 463: nobound = TRUE;
1.22 vincent 464:
1.9 vincent 465: done = 0;
466: do {
467: /*
468: * Move to where this has to apply
469: *
470: * Boundaries are put as position 0 (to save
1.24 vincent 471: * lookup time in find_dot) so we must
1.9 vincent 472: * not move there...
473: */
474: if (ptr->type != BOUNDARY) {
1.24 vincent 475: if (find_lo(ptr->pos, &lp,
1.10 vincent 476: &offset) == FALSE) {
1.9 vincent 477: ewprintf("Internal error in Undo!");
478: rval = FALSE;
479: break;
480: }
481: curwp->w_dotp = lp;
482: curwp->w_doto = offset;
483: }
484:
485: /*
486: * Do operation^-1
487: */
488: switch (ptr->type) {
489: case INSERT:
490: ldelete(ptr->region.r_size, KFORW);
491: break;
492: case DELETE:
1.13 deraadt 493: region_put_data(ptr->content,
1.9 vincent 494: ptr->region.r_size);
495: break;
496: case BOUNDARY:
497: done = 1;
498: break;
499: default:
500: break;
501: }
502:
503: /* And move to next record */
1.22 vincent 504: ptr = LIST_NEXT(ptr, next);
1.9 vincent 505: } while (ptr != NULL && !done);
1.22 vincent 506:
507: nobound = save;
508: undo_add_boundary();
1.9 vincent 509:
510: ewprintf("Undo!");
1.1 vincent 511: }
1.9 vincent 512: /*
513: * Record where we are. (we have to save our new position at the end
514: * since we change the dot when undoing....)
515: */
1.19 vincent 516: curwp->w_undoptr = ptr;
1.23 vincent 517:
1.24 vincent 518: curwp->w_undopos = find_dot(curwp->w_dotp, curwp->w_doto);
1.9 vincent 519:
1.13 deraadt 520: return (rval);
1.1 vincent 521: }