Annotation of src/usr.bin/mg/undo.c, Revision 1.23
1.23 ! vincent 1: /* $OpenBSD: undo.c,v 1.22 2003/11/09 01:44:39 vincent Exp $ */
1.1 vincent 2: /*
1.10 vincent 3: * Copyright (c) 2002 Vincent Labrecque
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: #include <sys/queue.h>
31:
32: #define MAX_FREE_RECORDS 32
33:
34: /*
35: * Local variables
36: */
1.7 vincent 37: static LIST_HEAD(, undo_rec) undo_free;
38: static int undo_free_num;
1.12 vincent 39: static int nobound;
1.1 vincent 40:
41: /*
42: * Global variables
43: */
44: /*
1.9 vincent 45: * undo_disable_flag: Stop doing undo (useful when we know are
46: * going to deal with huge deletion/insertions
47: * that we don't plan to undo)
1.1 vincent 48: */
49: int undo_disable_flag;
50:
51: /*
52: * Local functions
53: */
1.9 vincent 54: static int find_absolute_dot(LINE *, int);
55: static int find_line_offset(int, LINE **, int *);
1.1 vincent 56: static struct undo_rec *new_undo_record(void);
57: static int drop_oldest_undo_record(void);
58:
1.9 vincent 59: /*
60: * find_{absolute_dot,line_offset}()
61: *
62: * Find an absolute dot in the buffer from a line/offset pair, and vice-versa.
63: *
1.13 deraadt 64: * Since lines can be deleted while they are referenced by undo record, we
1.9 vincent 65: * need to have an absolute dot to have something reliable.
66: */
67:
1.1 vincent 68: static int
1.9 vincent 69: find_absolute_dot(LINE *lp, int off)
1.1 vincent 70: {
71: int count = 0;
72: LINE *p;
73:
1.21 vincent 74: for (p = curbp->b_linep; p != lp; p = lforw(p)) {
1.1 vincent 75: if (count != 0) {
1.21 vincent 76: if (p == curbp->b_linep) {
1.1 vincent 77: ewprintf("Error: Undo stuff called with a"
1.9 vincent 78: "nonexistent line");
1.11 vincent 79: return (FALSE);
1.1 vincent 80: }
81: }
82: count += llength(p) + 1;
83: }
84: count += off;
85:
1.11 vincent 86: return (count);
1.1 vincent 87: }
88:
89: static int
1.9 vincent 90: find_line_offset(int pos, LINE **olp, int *offset)
1.1 vincent 91: {
92: LINE *p;
93:
1.21 vincent 94: p = curbp->b_linep;
1.9 vincent 95: while (pos > llength(p)) {
1.1 vincent 96: pos -= llength(p) + 1;
1.21 vincent 97: if ((p = lforw(p)) == curbp->b_linep) {
1.1 vincent 98: *olp = NULL;
99: *offset = 0;
1.11 vincent 100: return (FALSE);
1.1 vincent 101: }
102: }
103: *olp = p;
104: *offset = pos;
105:
1.11 vincent 106: return (TRUE);
1.1 vincent 107: }
108:
109: static struct undo_rec *
110: new_undo_record(void)
111: {
112: struct undo_rec *rec;
113:
114: rec = LIST_FIRST(&undo_free);
1.9 vincent 115: if (rec != NULL) {
1.1 vincent 116: LIST_REMOVE(rec, next); /* Remove it from the free-list */
1.9 vincent 117: undo_free_num--;
118: } else {
1.1 vincent 119: if ((rec = malloc(sizeof *rec)) == NULL)
120: panic("Out of memory in undo code (record)");
121: }
122: memset(rec, 0, sizeof(struct undo_rec));
1.9 vincent 123:
1.11 vincent 124: return (rec);
1.1 vincent 125: }
126:
1.7 vincent 127: void
1.1 vincent 128: free_undo_record(struct undo_rec *rec)
129: {
1.9 vincent 130: static int initialised = 0;
131:
132: /*
133: * On the first run, do initialisation of the free list.
134: */
135: if (initialised == 0) {
136: LIST_INIT(&undo_free);
137: initialised = 1;
138: }
1.1 vincent 139: if (rec->content != NULL) {
140: free(rec->content);
141: rec->content = NULL;
142: }
143: if (undo_free_num >= MAX_FREE_RECORDS) {
144: free(rec);
145: return;
146: }
147: undo_free_num++;
1.9 vincent 148:
1.1 vincent 149: LIST_INSERT_HEAD(&undo_free, rec, next);
150: }
151:
152: /*
153: * Drop the oldest undo record in our list. Return 1 if we could remove it,
154: * 0 if the undo list was empty
155: */
156: static int
157: drop_oldest_undo_record(void)
158: {
159: struct undo_rec *rec;
160:
1.19 vincent 161: rec = LIST_END(&curwp->w_undo);
1.1 vincent 162: if (rec != NULL) {
163: undo_free_num--;
1.9 vincent 164: LIST_REMOVE(rec, next);
1.1 vincent 165: free_undo_record(rec);
1.11 vincent 166: return (1);
1.1 vincent 167: }
1.11 vincent 168: return (0);
1.1 vincent 169: }
1.9 vincent 170:
171: static __inline__ int
1.22 vincent 172: lastrectype(void)
1.1 vincent 173: {
1.9 vincent 174: struct undo_rec *rec;
175:
1.22 vincent 176: if ((rec = LIST_FIRST(&curwp->w_undo)) != NULL)
177: return (rec->type);
1.11 vincent 178: return (0);
1.1 vincent 179: }
180:
181: int
182: undo_enable(int on)
183: {
1.14 vincent 184: int pon = undo_disable_flag;
1.22 vincent 185:
1.14 vincent 186: undo_disable_flag = (on == TRUE) ? 0 : 1;
187: return (pon ? FALSE : TRUE);
1.5 vincent 188: }
189:
190: int
1.9 vincent 191: undo_add_boundary(void)
1.5 vincent 192: {
193: struct undo_rec *rec;
194:
1.12 vincent 195: if (nobound)
196: return (TRUE);
197:
1.5 vincent 198: rec = new_undo_record();
1.9 vincent 199: rec->type = BOUNDARY;
1.5 vincent 200:
1.19 vincent 201: LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.9 vincent 202:
1.11 vincent 203: return (TRUE);
1.1 vincent 204: }
205:
206: int
207: undo_add_insert(LINE *lp, int offset, int size)
208: {
209: REGION reg;
210: struct undo_rec *rec;
1.9 vincent 211: int pos;
212:
1.1 vincent 213: if (undo_disable_flag)
1.11 vincent 214: return (TRUE);
1.1 vincent 215: reg.r_linep = lp;
216: reg.r_offset = offset;
217: reg.r_size = size;
1.9 vincent 218:
219: pos = find_absolute_dot(lp, offset);
220:
1.1 vincent 221: /*
222: * We try to reuse the last undo record to `compress' things.
1.13 deraadt 223: */
1.19 vincent 224: rec = LIST_FIRST(&curwp->w_undo);
1.23 ! vincent 225: if (rec != NULL && rec->type == INSERT) {
! 226: if (rec->pos + rec->region.r_size == pos) {
! 227: rec->region.r_size += reg.r_size;
! 228: return (TRUE);
1.1 vincent 229: }
230: }
1.9 vincent 231:
1.1 vincent 232: /*
233: * We couldn't reuse the last undo record, so prepare a new one
234: */
235: rec = new_undo_record();
1.9 vincent 236: rec->pos = pos;
1.1 vincent 237: rec->type = INSERT;
238: memmove(&rec->region, ®, sizeof(REGION));
239: rec->content = NULL;
1.9 vincent 240:
1.22 vincent 241: if (lastrectype() != INSERT)
1.9 vincent 242: undo_add_boundary();
243:
1.19 vincent 244: LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.1 vincent 245:
1.11 vincent 246: return (TRUE);
1.1 vincent 247: }
248:
249: /*
250: * This of course must be done _before_ the actual deletion is done
251: */
252: int
253: undo_add_delete(LINE *lp, int offset, int size)
254: {
255: REGION reg;
256: struct undo_rec *rec;
1.9 vincent 257: int pos;
1.1 vincent 258:
259: if (undo_disable_flag)
1.11 vincent 260: return (TRUE);
1.1 vincent 261:
262: reg.r_linep = lp;
263: reg.r_offset = offset;
264: reg.r_size = size;
265:
1.9 vincent 266: pos = find_absolute_dot(lp, offset);
1.3 vincent 267:
1.10 vincent 268: if (offset == llength(lp)) /* if it's a newline... */
1.9 vincent 269: undo_add_boundary();
1.19 vincent 270: else if ((rec = LIST_FIRST(&curwp->w_undo)) != NULL) {
1.9 vincent 271: /*
272: * Separate this command from the previous one if we're not
273: * just before the previous record...
274: */
1.10 vincent 275: if (rec->type == DELETE) {
1.9 vincent 276: if (rec->pos - rec->region.r_size != pos)
277: undo_add_boundary();
1.23 ! vincent 278: }
1.3 vincent 279: }
1.1 vincent 280: rec = new_undo_record();
281: rec->pos = pos;
282:
283: rec->type = DELETE;
284: memmove(&rec->region, ®, sizeof(REGION));
285: do {
286: rec->content = malloc(reg.r_size + 1);
287: } while ((rec->content == NULL) && drop_oldest_undo_record());
1.9 vincent 288:
1.1 vincent 289: if (rec->content == NULL)
290: panic("Out of memory");
1.9 vincent 291:
1.1 vincent 292: region_get_data(®, rec->content, reg.r_size);
293:
1.22 vincent 294: if (lastrectype() != DELETE)
295: undo_add_boundary();
296:
1.19 vincent 297: LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.1 vincent 298:
1.11 vincent 299: return (TRUE);
1.1 vincent 300: }
301:
302: /*
303: * This of course must be called before the change takes place
304: */
305: int
306: undo_add_change(LINE *lp, int offset, int size)
307: {
308: if (undo_disable_flag)
1.11 vincent 309: return (TRUE);
1.12 vincent 310: undo_add_boundary();
311: nobound = 1;
312: undo_add_delete(lp, offset, size);
313: undo_add_insert(lp, offset, size);
314: nobound = 0;
1.9 vincent 315: undo_add_boundary();
316:
1.11 vincent 317: return (TRUE);
1.8 vincent 318: }
319:
320: /*
321: * Show the undo records for the current buffer in a new buffer.
322: */
323: int
1.18 vincent 324: undo_dump(int f, int n)
1.8 vincent 325: {
326: struct undo_rec *rec;
327: BUFFER *bp;
328: MGWIN *wp;
329: char buf[4096], tmp[1024];
330: int num;
331:
332: /*
333: * Prepare the buffer for insertion.
334: */
335: if ((bp = bfind("*undo*", TRUE)) == NULL)
1.11 vincent 336: return (FALSE);
1.9 vincent 337: bp->b_flag |= BFREADONLY;
1.8 vincent 338: bclear(bp);
339: popbuf(bp);
340:
1.10 vincent 341: for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
1.8 vincent 342: if (wp->w_bufp == bp) {
343: wp->w_dotp = bp->b_linep;
344: wp->w_doto = 0;
345: }
1.10 vincent 346: }
1.8 vincent 347:
348: num = 0;
1.19 vincent 349: for (rec = LIST_FIRST(&curwp->w_undo); rec != NULL;
1.13 deraadt 350: rec = LIST_NEXT(rec, next)) {
1.8 vincent 351: num++;
352: snprintf(buf, sizeof buf,
353: "Record %d =>\t %s at %d ", num,
354: (rec->type == DELETE) ? "DELETE":
355: (rec->type == INSERT) ? "INSERT":
356: (rec->type == BOUNDARY) ? "----" : "UNKNOWN",
357: rec->pos);
1.10 vincent 358:
1.12 vincent 359: if (rec->content) {
1.8 vincent 360: strlcat(buf, "\"", sizeof buf);
361: snprintf(tmp, sizeof tmp, "%.*s", rec->region.r_size,
362: rec->content);
363: strlcat(buf, tmp, sizeof buf);
364: strlcat(buf, "\"", sizeof buf);
365: }
1.15 avsm 366: snprintf(tmp, sizeof tmp, " [%d]", rec->region.r_size);
1.8 vincent 367: strlcat(buf, tmp, sizeof buf);
1.17 deraadt 368: addlinef(bp, "%s", buf);
1.8 vincent 369: }
1.11 vincent 370: return (TRUE);
1.1 vincent 371: }
372:
1.9 vincent 373: /*
374: * After the user did action1, then action2, then action3 :
375: *
376: * [action3] <--- Undoptr
377: * [action2]
378: * [action1]
379: * ------
380: * [undo]
381: *
382: * After undo:
383: *
384: * [undo of action3]
385: * [action2] <--- Undoptr
386: * [action1]
387: * ------
388: * [undo]
1.13 deraadt 389: *
1.9 vincent 390: * After another undo:
391: *
392: *
393: * [undo of action2]
394: * [undo of action3]
395: * [action1] <--- Undoptr
396: * ------
397: * [undo]
1.13 deraadt 398: *
1.9 vincent 399: * Note that the "undo of actionX" have no special meaning. Only when,
400: * say, we undo a deletion, the insertion will be recorded just as if it
1.10 vincent 401: * was typed on the keyboard. Resulting in the inverse operation being
1.9 vincent 402: * saved in the list.
403: *
404: * If undoptr reaches the bottom of the list, or if we moved between
405: * two undo actions, we make it point back at the topmost record. This is
406: * how we handle redoing.
407: */
1.1 vincent 408: int
1.4 vincent 409: undo(int f, int n)
1.1 vincent 410: {
1.9 vincent 411: struct undo_rec *ptr, *nptr;
412: int done, rval;
413: LINE *lp;
1.23 ! vincent 414: int offset, save, dot;
! 415:
! 416: dot = find_absolute_dot(curwp->w_dotp, curwp->w_doto);
1.9 vincent 417:
1.19 vincent 418: ptr = curwp->w_undoptr;
1.9 vincent 419:
420: /* if we moved, make ptr point back to the top of the list */
1.23 ! vincent 421: if (ptr == NULL || curwp->w_undopos != dot)
1.19 vincent 422: ptr = LIST_FIRST(&curwp->w_undo);
1.1 vincent 423:
1.9 vincent 424: rval = TRUE;
1.10 vincent 425: while (n--) {
1.9 vincent 426: /* if we have a spurious boundary, free it and move on.... */
427: while (ptr && ptr->type == BOUNDARY) {
428: nptr = LIST_NEXT(ptr, next);
429: LIST_REMOVE(ptr, next);
430: free_undo_record(ptr);
431: ptr = nptr;
1.4 vincent 432: }
433: /*
1.9 vincent 434: * Ptr is NULL, but on the next run, it will point to the
435: * top again, redoing all stuff done in the buffer since
436: * its creation.
1.4 vincent 437: */
1.9 vincent 438: if (ptr == NULL) {
1.11 vincent 439: ewprintf("No further undo information");
1.9 vincent 440: rval = FALSE;
1.4 vincent 441: break;
442: }
1.6 vincent 443:
1.13 deraadt 444: /*
1.9 vincent 445: * Loop while we don't get a boundary specifying we've
446: * finished the current action...
447: */
1.22 vincent 448:
449: if (lastrectype() != BOUNDARY)
450: undo_add_boundary();
451:
452: save = nobound;
453: nobound = 1;
454:
1.9 vincent 455: done = 0;
456: do {
457: /*
458: * Move to where this has to apply
459: *
460: * Boundaries are put as position 0 (to save
461: * lookup time in find_absolute_dot) so we must
462: * not move there...
463: */
464: if (ptr->type != BOUNDARY) {
1.10 vincent 465: if (find_line_offset(ptr->pos, &lp,
466: &offset) == FALSE) {
1.9 vincent 467: ewprintf("Internal error in Undo!");
468: rval = FALSE;
469: break;
470: }
471: curwp->w_dotp = lp;
472: curwp->w_doto = offset;
473: }
474:
475: /*
476: * Do operation^-1
477: */
478: switch (ptr->type) {
479: case INSERT:
480: ldelete(ptr->region.r_size, KFORW);
481: break;
482: case DELETE:
1.13 deraadt 483: region_put_data(ptr->content,
1.9 vincent 484: ptr->region.r_size);
485: break;
486: case BOUNDARY:
487: done = 1;
488: break;
489: default:
490: break;
491: }
492:
493: /* And move to next record */
1.22 vincent 494: ptr = LIST_NEXT(ptr, next);
1.9 vincent 495: } while (ptr != NULL && !done);
1.22 vincent 496:
497: nobound = save;
498: undo_add_boundary();
1.9 vincent 499:
500: ewprintf("Undo!");
1.1 vincent 501: }
1.9 vincent 502: /*
503: * Record where we are. (we have to save our new position at the end
504: * since we change the dot when undoing....)
505: */
1.19 vincent 506: curwp->w_undoptr = ptr;
1.23 ! vincent 507:
! 508: curwp->w_undopos = find_absolute_dot(curwp->w_dotp, curwp->w_doto);
1.9 vincent 509:
1.13 deraadt 510: return (rval);
1.1 vincent 511: }