Annotation of src/usr.bin/mg/undo.c, Revision 1.3
1.3 ! vincent 1: /* $OpenBSD: undo.c,v 1.2 2002/02/20 23:15:03 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;
187: }
188:
189: int
190: undo_add_boundary(void)
191: {
192: struct undo_rec *rec;
193:
194: if (undo_disable_flag)
195: return TRUE;
196:
197: rec = new_undo_record();
198: rec->buf = curbp;
199: rec->type = BOUNDARY;
200:
201: LIST_INSERT_HEAD(&undo_list, rec, next);
202:
203: return TRUE;
204: }
205:
206: int
207: undo_add_insert(LINE *lp, int offset, int size)
208: {
209: REGION reg;
210: struct undo_rec *rec;
211:
212: if (undo_disable_flag)
213: return TRUE;
214:
215: reg.r_linep = lp;
216: reg.r_offset = offset;
217: reg.r_size = size;
218:
219: /*
220: * We try to reuse the last undo record to `compress' things.
221: */
222: rec = LIST_FIRST(&undo_list);
223: if ((rec != NULL) &&
224: (rec->type == INSERT) &&
225: (rec->buf == curbp) &&
226: (rec->region.r_linep == lp)) {
227: int dist;
228:
229: dist = rec->region.r_offset - reg.r_offset;
230:
231: if (rec->region.r_size >= dist) {
232: rec->region.r_size += reg.r_size;
233: return TRUE;
234: }
235: }
236: /*
237: * We couldn't reuse the last undo record, so prepare a new one
238: */
239: rec = new_undo_record();
240: rec->pos = find_offset(lp, offset);
241: rec->buf = curbp;
242: rec->type = INSERT;
243: memmove(&rec->region, ®, sizeof(REGION));
244: rec->content = NULL;
245: LIST_INSERT_HEAD(&undo_list, rec, next);
246:
247: return TRUE;
248: }
249:
250: /*
251: * This of course must be done _before_ the actual deletion is done
252: */
253: int
254: undo_add_delete(LINE *lp, int offset, int size)
255: {
256: REGION reg;
257: struct undo_rec *rec;
1.2 vincent 258: int dist, pos, skip = 0;
1.1 vincent 259:
260: if (undo_disable_flag)
261: return TRUE;
262:
263: reg.r_linep = lp;
264: reg.r_offset = offset;
265: reg.r_size = size;
266:
267: pos = find_offset(lp, offset);
1.3 ! vincent 268:
! 269: if (size == 1 && llength(lp) == 0) {
1.2 vincent 270: skip = 1;
1.3 ! vincent 271: }
1.1 vincent 272:
273: /*
274: * Again, try to reuse last undo record, if we can
275: */
276: rec = LIST_FIRST(&undo_list);
1.2 vincent 277: if (!skip &&
278: (rec != NULL) &&
1.1 vincent 279: (rec->type == DELETE) &&
280: (rec->buf == curbp) &&
281: (rec->region.r_linep == reg.r_linep)) {
282: char *newbuf;
283: int newlen;
284:
285: dist = rec->region.r_offset - reg.r_offset;
286: if (rec->region.r_size >= dist) {
287: newlen = rec->region.r_size + reg.r_size;
288:
289: do {
290: newbuf = malloc(newlen * sizeof(char));
291: } while (newbuf == NULL && drop_oldest_undo_record());
292:
293: if (newbuf == NULL)
294: panic("out of memory in undo delete code");
295:
296: /*
297: * [new data][old data]
298: */
299: region_get_data(®, newbuf, size);
300: memmove(newbuf + reg.r_size, rec->content,
301: rec->region.r_size);
302:
303: rec->pos = pos;
304: rec->region.r_offset = reg.r_offset;
305: rec->region.r_size = newlen;
306: if (rec->content != NULL)
307: free(rec->content);
308: rec->content = newbuf;
309:
310: return TRUE;
311: }
312: }
313:
314: /*
315: * So we couldn't reuse the last undo record? Just allocate a new
316: * one.
317: */
318: rec = new_undo_record();
319: rec->pos = pos;
320:
321: rec->buf = curbp;
322: rec->type = DELETE;
323: memmove(&rec->region, ®, sizeof(REGION));
324: do {
325: rec->content = malloc(reg.r_size + 1);
326: } while ((rec->content == NULL) && drop_oldest_undo_record());
327:
328: if (rec->content == NULL)
329: panic("Out of memory");
330:
331: region_get_data(®, rec->content, reg.r_size);
332:
333: LIST_INSERT_HEAD(&undo_list, rec, next);
334:
335: return TRUE;
336: }
337:
338: /*
339: * This of course must be called before the change takes place
340: */
341: int
342: undo_add_change(LINE *lp, int offset, int size)
343: {
344: REGION reg;
345: struct undo_rec *rec;
346:
347:
348: if (undo_disable_flag)
349: return TRUE;
350:
351: reg.r_linep = lp;
352: reg.r_offset = offset;
353: reg.r_size = size;
354:
355: rec = new_undo_record();
356: rec->pos = find_offset(lp, offset);
357: rec->buf = curbp;
358: rec->type = CHANGE;
359: memmove(&rec->region, ®, sizeof reg);
360:
361: do {
362: rec->content = malloc(size + 1);
363: } while ((rec->content == NULL) && drop_oldest_undo_record());
364:
365: if (rec->content == NULL)
366: panic("Out of memory in undo change code");
367:
368: region_get_data(®, rec->content, size);
369:
370: LIST_INSERT_HEAD(&undo_list, rec, next);
371:
372: return TRUE;
373: }
374:
375: int
376: undo(void)
377: {
378: struct undo_rec *rec;
379: LINE *ln;
380: int off;
381:
382: again:
383: rec = LIST_FIRST(&undo_list);
384: if (rec == NULL) {
385: ewprintf("Nothing to undo!");
386: return FALSE;
387: }
388: if (rec->buf != curbp)
389: popbuf(rec->buf);
390:
391: LIST_REMOVE(rec, next);
392: if (rec->type == BOUNDARY)
393: goto again;
394:
395: /*
396: * Let called functions know they are below us (for example, ldelete
397: * don't want to record an undo record when called by us)
398: */
399: undoaction++;
400:
401: find_linep(rec->pos, &ln, &off);
402: if (ln == NULL)
403: return FALSE;
404:
405: /*
406: * Move to where this record has to apply
407: */
408: curwp->w_dotp = ln;
409: curwp->w_doto = off;
410:
411: switch (rec->type) {
412: case INSERT:
413: ldelete(rec->region.r_size, KFORW);
414: break;
415: case DELETE:
416: region_put_data(rec->content, rec->region.r_size);
417: break;
418: case CHANGE:
419: forwchar(0, rec->region.r_size);
420: lreplace(rec->region.r_size, rec->content, 1);
421: break;
422: default:
423: break;
424: }
425:
426: free_undo_record(rec);
427:
428: undoaction--;
429:
430: return TRUE;
431: }