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