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