Annotation of src/usr.bin/mg/undo.c, Revision 1.8
1.8 ! vincent 1: /* $OpenBSD: undo.c,v 1.7 2002/02/26 00:45:45 vincent Exp $ */
1.1 vincent 2: /*
3: * Copyright (c) 2002 Vincent Labrecque <vincent@openbsd.org>
4: * All rights reserved.
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.1 vincent 39:
40: /*
41: * Global variables
42: */
43: /*
44: * undo_disable_flag -
45: *
46: * Stop doing undo (useful when we know are
47: * going to deal with huge deletion/insertions
48: * that we don't plan to undo)
49: */
50: int undo_disable_flag;
51: int undoaction; /* Are we called indirectly from undo()? */
52:
53: /*
54: * Local functions
55: */
56: static int find_offset(LINE *, int);
57: static int find_linep(int, LINE **, int *);
58: static struct undo_rec *new_undo_record(void);
59: static int drop_oldest_undo_record(void);
60:
61: static int
62: find_offset(LINE *lp, int off)
63: {
64: int count = 0;
65: LINE *p;
66:
67: for (p = curwp->w_linep; p != lp; p = lforw(p)) {
68: if (count != 0) {
1.3 vincent 69: if (p == curwp->w_linep) {
1.1 vincent 70: ewprintf("Error: Undo stuff called with a"
71: "nonexistent line\n");
72: return FALSE;
73: }
74: }
75: count += llength(p) + 1;
76: }
77: count += off;
78:
79: return count;
80: }
81:
82: static int
83: find_linep(int pos, LINE **olp, int *offset)
84: {
85: LINE *p;
86:
87: p = curwp->w_linep;
88: while (pos > 0 && pos > llength(p)) {
89: pos -= llength(p) + 1;
1.2 vincent 90: if ((p = lforw(p)) == curwp->w_linep) {
1.1 vincent 91: *olp = NULL;
92: *offset = 0;
93: return FALSE;
94: }
95: }
96: *olp = p;
97: *offset = pos;
98:
99: return TRUE;
100: }
101:
102: static struct undo_rec *
103: new_undo_record(void)
104: {
105: struct undo_rec *rec;
106:
107: rec = LIST_FIRST(&undo_free);
108: if (rec != NULL)
109: LIST_REMOVE(rec, next); /* Remove it from the free-list */
110: else {
111: if ((rec = malloc(sizeof *rec)) == NULL)
112: panic("Out of memory in undo code (record)");
113: }
114: memset(rec, 0, sizeof(struct undo_rec));
115:
116: return rec;
117: }
118:
1.7 vincent 119: void
1.1 vincent 120: free_undo_record(struct undo_rec *rec)
121: {
122: if (rec->content != NULL) {
123: free(rec->content);
124: rec->content = NULL;
125: }
126: if (undo_free_num >= MAX_FREE_RECORDS) {
127: free(rec);
128: return;
129: }
130: undo_free_num++;
131:
132: LIST_INSERT_HEAD(&undo_free, rec, next);
133: }
134:
135: /*
136: * Drop the oldest undo record in our list. Return 1 if we could remove it,
137: * 0 if the undo list was empty
138: */
139: static int
140: drop_oldest_undo_record(void)
141: {
142: struct undo_rec *rec;
143:
144: rec = LIST_END(&undo_list);
145: if (rec != NULL) {
146: undo_free_num--;
147: LIST_REMOVE(rec, next); /* Remove it from the undo_list before
148: * we insert it in the free list */
149: free_undo_record(rec);
150: return 1;
151: }
152: return 0;
153: }
154:
155: int
156: undo_init(void)
157: {
158: LIST_INIT(&undo_free);
159:
160: return TRUE;
161: }
162:
163: int
164: undo_enable(int on)
165: {
166: undo_disable_flag = on ? 0 : 1;
167:
168: /*
169: * XXX-Vince:
170: *
171: * Here, I wonder if we should assume that the user has made a
172: * long term choice. If so, we could free all internal undo
173: * data and save memory.
174: */
175:
176: return on;
1.5 vincent 177: }
178:
179: int
180: undo_add_custom(int type, LINE *lp, int offset, void *content, int size)
181: {
182: struct undo_rec *rec;
183:
184: if (undo_disable_flag)
185: return TRUE;
186: rec = new_undo_record();
187: rec->pos = find_offset(lp, offset);
188: rec->type = type;
189: rec->content = content;
190: rec->region.r_linep = lp;
191: rec->region.r_offset = offset;
192: rec->region.r_size = size;
193:
1.7 vincent 194: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.5 vincent 195:
196: return TRUE;
1.1 vincent 197: }
198:
199: int
200: undo_add_boundary(void)
201: {
202: struct undo_rec *rec;
203:
204: if (undo_disable_flag)
205: return TRUE;
206:
207: rec = new_undo_record();
208: rec->type = BOUNDARY;
209:
1.7 vincent 210: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 211:
212: return TRUE;
213: }
214:
215: int
216: undo_add_insert(LINE *lp, int offset, int size)
217: {
218: REGION reg;
219: struct undo_rec *rec;
220:
221: if (undo_disable_flag)
222: return TRUE;
223:
224: reg.r_linep = lp;
225: reg.r_offset = offset;
226: reg.r_size = size;
227:
228: /*
229: * We try to reuse the last undo record to `compress' things.
230: */
1.7 vincent 231: rec = LIST_FIRST(&curbp->b_undo);
1.1 vincent 232: if ((rec != NULL) &&
233: (rec->type == INSERT) &&
234: (rec->region.r_linep == lp)) {
235: int dist;
236:
237: dist = rec->region.r_offset - reg.r_offset;
238:
239: if (rec->region.r_size >= dist) {
240: rec->region.r_size += reg.r_size;
241: return TRUE;
242: }
243: }
1.6 vincent 244:
1.1 vincent 245: /*
246: * We couldn't reuse the last undo record, so prepare a new one
247: */
248: rec = new_undo_record();
249: rec->pos = find_offset(lp, offset);
250: rec->type = INSERT;
251: memmove(&rec->region, ®, sizeof(REGION));
252: rec->content = NULL;
1.7 vincent 253: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 254:
255: return TRUE;
256: }
257:
258: /*
259: * This of course must be done _before_ the actual deletion is done
260: */
261: int
262: undo_add_delete(LINE *lp, int offset, int size)
263: {
264: REGION reg;
265: struct undo_rec *rec;
1.2 vincent 266: int dist, pos, skip = 0;
1.1 vincent 267:
268: if (undo_disable_flag)
269: return TRUE;
270:
271: reg.r_linep = lp;
272: reg.r_offset = offset;
273: reg.r_size = size;
274:
275: pos = find_offset(lp, offset);
1.3 vincent 276:
277: if (size == 1 && llength(lp) == 0) {
1.2 vincent 278: skip = 1;
1.3 vincent 279: }
1.1 vincent 280:
281: /*
282: * Again, try to reuse last undo record, if we can
283: */
1.7 vincent 284: rec = LIST_FIRST(&curbp->b_undo);
1.2 vincent 285: if (!skip &&
286: (rec != NULL) &&
1.1 vincent 287: (rec->type == DELETE) &&
288: (rec->region.r_linep == reg.r_linep)) {
289: char *newbuf;
290: int newlen;
291:
292: dist = rec->region.r_offset - reg.r_offset;
293: if (rec->region.r_size >= dist) {
294: newlen = rec->region.r_size + reg.r_size;
295:
296: do {
297: newbuf = malloc(newlen * sizeof(char));
298: } while (newbuf == NULL && drop_oldest_undo_record());
299:
300: if (newbuf == NULL)
301: panic("out of memory in undo delete code");
302:
303: /*
304: * [new data][old data]
305: */
306: region_get_data(®, newbuf, size);
307: memmove(newbuf + reg.r_size, rec->content,
308: rec->region.r_size);
309:
310: rec->pos = pos;
311: rec->region.r_offset = reg.r_offset;
312: rec->region.r_size = newlen;
313: if (rec->content != NULL)
314: free(rec->content);
315: rec->content = newbuf;
316:
317: return TRUE;
318: }
319: }
320:
321: /*
322: * So we couldn't reuse the last undo record? Just allocate a new
323: * one.
324: */
325: rec = new_undo_record();
326: rec->pos = pos;
327:
328: rec->type = DELETE;
329: memmove(&rec->region, ®, sizeof(REGION));
330: do {
331: rec->content = malloc(reg.r_size + 1);
332: } while ((rec->content == NULL) && drop_oldest_undo_record());
333:
334: if (rec->content == NULL)
335: panic("Out of memory");
336:
337: region_get_data(®, rec->content, reg.r_size);
338:
1.7 vincent 339: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 340:
341: return TRUE;
342: }
343:
344: /*
345: * This of course must be called before the change takes place
346: */
347: int
348: undo_add_change(LINE *lp, int offset, int size)
349: {
350: REGION reg;
351: struct undo_rec *rec;
352:
353:
354: if (undo_disable_flag)
355: return TRUE;
356:
357: reg.r_linep = lp;
358: reg.r_offset = offset;
359: reg.r_size = size;
360:
361: rec = new_undo_record();
362: rec->pos = find_offset(lp, offset);
363: rec->type = CHANGE;
364: memmove(&rec->region, ®, sizeof reg);
365:
366: do {
367: rec->content = malloc(size + 1);
368: } while ((rec->content == NULL) && drop_oldest_undo_record());
369:
370: if (rec->content == NULL)
371: panic("Out of memory in undo change code");
372:
373: region_get_data(®, rec->content, size);
374:
1.7 vincent 375: LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1 vincent 376:
1.8 ! vincent 377: return TRUE;
! 378: }
! 379:
! 380: /*
! 381: * Show the undo records for the current buffer in a new buffer.
! 382: */
! 383: int
! 384: undo_dump(void)
! 385: {
! 386: struct undo_rec *rec;
! 387: BUFFER *bp;
! 388: MGWIN *wp;
! 389: char buf[4096], tmp[1024];
! 390: int num;
! 391:
! 392: /*
! 393: * Prepare the buffer for insertion.
! 394: */
! 395: if ((bp = bfind("*undo*", TRUE)) == NULL)
! 396: return FALSE;
! 397:
! 398: bclear(bp);
! 399: popbuf(bp);
! 400:
! 401: for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
! 402: if (wp->w_bufp == bp) {
! 403: wp->w_dotp = bp->b_linep;
! 404: wp->w_doto = 0;
! 405: }
! 406:
! 407: num = 0;
! 408: for (rec = LIST_FIRST(&curbp->b_undo); rec != NULL;
! 409: rec = LIST_NEXT(rec, next)) {
! 410: num++;
! 411: snprintf(buf, sizeof buf,
! 412: "Record %d =>\t %s at %d ", num,
! 413: (rec->type == DELETE) ? "DELETE":
! 414: (rec->type == INSERT) ? "INSERT":
! 415: (rec->type == CHANGE) ? "CHANGE":
! 416: (rec->type == BOUNDARY) ? "----" : "UNKNOWN",
! 417: rec->pos);
! 418: if (rec->type == DELETE || rec->type == CHANGE) {
! 419:
! 420: strlcat(buf, "\"", sizeof buf);
! 421: snprintf(tmp, sizeof tmp, "%.*s", rec->region.r_size,
! 422: rec->content);
! 423: strlcat(buf, tmp, sizeof buf);
! 424: strlcat(buf, "\"", sizeof buf);
! 425: }
! 426: snprintf(tmp, sizeof buf, " [%d]", rec->region.r_size);
! 427: strlcat(buf, tmp, sizeof buf);
! 428:
! 429: addlinef(bp, buf);
! 430: }
1.1 vincent 431: return TRUE;
432: }
433:
434: int
1.4 vincent 435: undo(int f, int n)
1.1 vincent 436: {
437: struct undo_rec *rec;
438: LINE *ln;
439: int off;
440:
441: /*
1.4 vincent 442: * Let called functions know they are below us (for
443: * example, ldelete don't want to record an undo record
444: * when called by us)
1.1 vincent 445: */
446: undoaction++;
447:
1.6 vincent 448: while (n > 0) {
1.7 vincent 449: rec = LIST_FIRST(&curbp->b_undo);
1.4 vincent 450: if (rec == NULL) {
451: ewprintf("Nothing to undo!");
452: return FALSE;
453: }
454: LIST_REMOVE(rec, next);
455: if (rec->type == BOUNDARY) {
456: continue;
457: }
1.1 vincent 458:
1.4 vincent 459: find_linep(rec->pos, &ln, &off);
460: if (ln == NULL)
461: return FALSE;
462:
463: /*
464: * Move to where this record has to apply
465: */
466: curwp->w_dotp = ln;
467: curwp->w_doto = off;
468:
469: switch (rec->type) {
470: case INSERT:
471: ldelete(rec->region.r_size, KFORW);
472: break;
473: case DELETE:
474: region_put_data(rec->content, rec->region.r_size);
475: break;
476: case CHANGE:
477: forwchar(0, rec->region.r_size);
478: lreplace(rec->region.r_size, rec->content, 1);
479: break;
480: default:
481: break;
482: }
483:
484: free_undo_record(rec);
1.6 vincent 485:
486: n--;
1.1 vincent 487: }
488: undoaction--;
489:
490: return TRUE;
491: }