Annotation of src/usr.bin/mg/undo.c, Revision 1.7
1.7 ! vincent 1: /* $OpenBSD: undo.c,v 1.6 2002/02/21 17:36:12 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:
377: return TRUE;
378: }
379:
380: int
1.4 vincent 381: undo(int f, int n)
1.1 vincent 382: {
383: struct undo_rec *rec;
384: LINE *ln;
385: int off;
386:
387: /*
1.4 vincent 388: * Let called functions know they are below us (for
389: * example, ldelete don't want to record an undo record
390: * when called by us)
1.1 vincent 391: */
392: undoaction++;
393:
1.6 vincent 394: while (n > 0) {
1.7 ! vincent 395: rec = LIST_FIRST(&curbp->b_undo);
1.4 vincent 396: if (rec == NULL) {
397: ewprintf("Nothing to undo!");
398: return FALSE;
399: }
400: LIST_REMOVE(rec, next);
401: if (rec->type == BOUNDARY) {
402: continue;
403: }
1.1 vincent 404:
1.4 vincent 405: find_linep(rec->pos, &ln, &off);
406: if (ln == NULL)
407: return FALSE;
408:
409: /*
410: * Move to where this record has to apply
411: */
412: curwp->w_dotp = ln;
413: curwp->w_doto = off;
414:
415: switch (rec->type) {
416: case INSERT:
417: ldelete(rec->region.r_size, KFORW);
418: break;
419: case DELETE:
420: region_put_data(rec->content, rec->region.r_size);
421: break;
422: case CHANGE:
423: forwchar(0, rec->region.r_size);
424: lreplace(rec->region.r_size, rec->content, 1);
425: break;
426: default:
427: break;
428: }
429:
430: free_undo_record(rec);
1.6 vincent 431:
432: n--;
1.1 vincent 433: }
434: undoaction--;
435:
436: return TRUE;
437: }