1 /* $OpenBSD: undo.c,v 1.50 2010/06/30 19:12:54 oga Exp $ */
3 * This file is in the public domain
9 #define MAX_FREE_RECORDS 32
14 static struct undoq undo_free;
15 static int undo_free_num;
16 static int boundary_flag = TRUE;
17 static int undo_enable_flag = TRUE;
22 static int find_dot(struct line *, int);
23 static int find_lo(int, struct line **, int *, int *);
24 static struct undo_rec *new_undo_record(void);
25 static int drop_oldest_undo_record(void);
30 * Find an absolute dot in the buffer from a line/offset pair, and vice-versa.
32 * Since lines can be deleted while they are referenced by undo record, we
33 * need to have an absolute dot to have something reliable.
36 find_dot(struct line *lp, int off)
41 for (p = curbp->b_headp; p != lp; p = lforw(p)) {
43 if (p == curbp->b_headp) {
44 ewprintf("Error: Undo stuff called with a"
49 count += llength(p) + 1;
57 find_lo(int pos, struct line **olp, int *offset, int *lnum)
64 while (pos > llength(p)) {
65 pos -= llength(p) + 1;
66 if ((p = lforw(p)) == curbp->b_headp) {
80 static struct undo_rec *
85 rec = TAILQ_FIRST(&undo_free);
87 /* Remove it from the free-list */
88 TAILQ_REMOVE(&undo_free, rec, next);
91 if ((rec = malloc(sizeof(*rec))) == NULL)
92 panic("Out of memory in undo code (record)");
94 memset(rec, 0, sizeof(struct undo_rec));
100 free_undo_record(struct undo_rec *rec)
102 static int initialised = 0;
105 * On the first run, do initialisation of the free list.
107 if (initialised == 0) {
108 TAILQ_INIT(&undo_free);
111 if (rec->content != NULL) {
115 if (undo_free_num >= MAX_FREE_RECORDS) {
121 TAILQ_INSERT_HEAD(&undo_free, rec, next);
125 * Drop the oldest undo record in our list. Return 1 if we could remove it,
126 * 0 if the undo list was empty.
129 drop_oldest_undo_record(void)
131 struct undo_rec *rec;
133 rec = TAILQ_LAST(&curbp->b_undo, undoq);
136 TAILQ_REMOVE(&curbp->b_undo, rec, next);
137 free_undo_record(rec);
146 struct undo_rec *rec;
148 if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL)
154 * Returns TRUE if undo is enabled, FALSE otherwise.
159 return (undo_enable_flag);
163 * undo_enable: toggle undo_enable.
164 * Returns the previous value of the flag.
167 undo_enable(int f, int n)
169 int pon = undo_enable_flag;
171 if (f & (FFARG | FFRAND))
172 undo_enable_flag = n > 0;
174 undo_enable_flag = !undo_enable_flag;
177 ewprintf("Undo %sabled", undo_enable_flag ? "en" : "dis");
183 * If undo is enabled, then:
184 * Toggle undo boundary recording.
185 * If called with an argument, (n > 0) => enable. Otherwise disable.
186 * In either case, add an undo boundary
187 * If undo is disabled, this function has no effect.
190 undo_boundary_enable(int f, int n)
192 int bon = boundary_flag;
194 if (!undo_enable_flag)
197 undo_add_boundary(FFRAND, 1);
199 if (f & (FFARG | FFRAND))
200 boundary_flag = n > 0;
202 boundary_flag = !boundary_flag;
205 ewprintf("Undo boundaries %sabled",
206 boundary_flag ? "en" : "dis");
212 * Record an undo boundary, unless boundary_flag == FALSE.
213 * Does nothing if previous undo entry is already a boundary or 'modified' flag.
216 undo_add_boundary(int f, int n)
218 struct undo_rec *rec;
221 if (boundary_flag == FALSE)
224 last = lastrectype();
225 if (last == BOUNDARY || last == MODIFIED)
228 rec = new_undo_record();
229 rec->type = BOUNDARY;
231 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
237 * Record an undo "modified" boundary
240 undo_add_modified(void)
242 struct undo_rec *rec;
244 rec = new_undo_record();
245 rec->type = MODIFIED;
247 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
253 undo_add_insert(struct line *lp, int offset, int size)
256 struct undo_rec *rec;
259 if (!undo_enable_flag)
262 reg.r_offset = offset;
265 pos = find_dot(lp, offset);
268 * We try to reuse the last undo record to `compress' things.
270 rec = TAILQ_FIRST(&curbp->b_undo);
271 if (rec != NULL && rec->type == INSERT) {
272 if (rec->pos + rec->region.r_size == pos) {
273 rec->region.r_size += reg.r_size;
279 * We couldn't reuse the last undo record, so prepare a new one.
281 rec = new_undo_record();
284 memmove(&rec->region, ®, sizeof(struct region));
287 undo_add_boundary(FFRAND, 1);
289 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
295 * This of course must be done _before_ the actual deletion is done.
298 undo_add_delete(struct line *lp, int offset, int size, int isreg)
301 struct undo_rec *rec;
304 if (!undo_enable_flag)
308 reg.r_offset = offset;
311 pos = find_dot(lp, offset);
313 if (offset == llength(lp)) /* if it's a newline... */
314 undo_add_boundary(FFRAND, 1);
315 else if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) {
317 * Separate this command from the previous one if we're not
318 * just before the previous record...
320 if (!isreg && rec->type == DELETE) {
321 if (rec->pos - rec->region.r_size != pos)
322 undo_add_boundary(FFRAND, 1);
325 rec = new_undo_record();
331 memmove(&rec->region, ®, sizeof(struct region));
333 rec->content = malloc(reg.r_size + 1);
334 } while ((rec->content == NULL) && drop_oldest_undo_record());
336 if (rec->content == NULL)
337 panic("Out of memory");
339 region_get_data(®, rec->content, reg.r_size);
341 if (isreg || lastrectype() != DELETE)
342 undo_add_boundary(FFRAND, 1);
344 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
350 * This of course must be called before the change takes place.
353 undo_add_change(struct line *lp, int offset, int size)
355 if (!undo_enable_flag)
357 undo_add_boundary(FFRAND, 1);
358 boundary_flag = FALSE;
359 undo_add_delete(lp, offset, size, 0);
360 undo_add_insert(lp, offset, size);
361 boundary_flag = TRUE;
362 undo_add_boundary(FFRAND, 1);
368 * Show the undo records for the current buffer in a new buffer.
372 undo_dump(int f, int n)
374 struct undo_rec *rec;
377 char buf[4096], tmp[1024];
381 * Prepare the buffer for insertion.
383 if ((bp = bfind("*undo*", TRUE)) == NULL)
385 bp->b_flag |= BFREADONLY;
389 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
390 if (wp->w_bufp == bp) {
391 wp->w_dotp = bp->b_headp;
397 TAILQ_FOREACH(rec, &curbp->b_undo, next) {
399 snprintf(buf, sizeof(buf),
400 "%d:\t %s at %d ", num,
401 (rec->type == DELETE) ? "DELETE":
402 (rec->type == DELREG) ? "DELREGION":
403 (rec->type == INSERT) ? "INSERT":
404 (rec->type == BOUNDARY) ? "----" :
405 (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN",
409 (void)strlcat(buf, "\"", sizeof(buf));
410 snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
412 (void)strlcat(buf, tmp, sizeof(buf));
413 (void)strlcat(buf, "\"", sizeof(buf));
415 snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
416 if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) {
417 ewprintf("Undo record too large. Aborted.");
420 addlinef(bp, "%s", buf);
426 * After the user did action1, then action2, then action3:
428 * [action3] <--- Undoptr
437 * [action2] <--- Undoptr
442 * After another undo:
447 * [action1] <--- Undoptr
451 * Note that the "undo of actionX" have no special meaning. Only when
452 * we undo a deletion, the insertion will be recorded just as if it
453 * was typed on the keyboard. Resulting in the inverse operation being
456 * If undoptr reaches the bottom of the list, or if we moved between
457 * two undo actions, we make it point back at the topmost record. This is
458 * how we handle redoing.
464 struct undo_rec *ptr, *nptr;
467 int offset, save, dot;
468 static int nulled = FALSE;
474 dot = find_dot(curwp->w_dotp, curwp->w_doto);
476 ptr = curbp->b_undoptr;
478 /* first invocation, make ptr point back to the top of the list */
479 if ((ptr == NULL && nulled == TRUE) || rptcount == 0) {
480 ptr = TAILQ_FIRST(&curbp->b_undo);
486 /* if we have a spurious boundary, free it and move on.... */
487 while (ptr && ptr->type == BOUNDARY) {
488 nptr = TAILQ_NEXT(ptr, next);
489 TAILQ_REMOVE(&curbp->b_undo, ptr, next);
490 free_undo_record(ptr);
494 * Ptr is NULL, but on the next run, it will point to the
495 * top again, redoing all stuff done in the buffer since
499 ewprintf("No further undo information");
507 * Loop while we don't get a boundary specifying we've
508 * finished the current action...
511 undo_add_boundary(FFRAND, 1);
513 save = boundary_flag;
514 boundary_flag = FALSE;
519 * Move to where this has to apply
521 * Boundaries (and the modified flag) are put as
522 * position 0 (to save lookup time in find_dot)
523 * so we must not move there...
525 if (ptr->type != BOUNDARY && ptr->type != MODIFIED) {
526 if (find_lo(ptr->pos, &lp,
527 &offset, &lineno) == FALSE) {
528 ewprintf("Internal error in Undo!");
533 curwp->w_doto = offset;
534 curwp->w_markline = curwp->w_dotline;
535 curwp->w_dotline = lineno;
543 ldelete(ptr->region.r_size, KNONE);
547 offset = curwp->w_doto;
548 region_put_data(ptr->content,
551 curwp->w_doto = offset;
554 region_put_data(ptr->content,
561 curbp->b_flag &= ~BFCHG;
567 /* And move to next record */
568 ptr = TAILQ_NEXT(ptr, next);
569 } while (ptr != NULL && !done);
571 boundary_flag = save;
572 undo_add_boundary(FFRAND, 1);
577 * Record where we are. (we have to save our new position at the end
578 * since we change the dot when undoing....)
580 curbp->b_undoptr = ptr;
582 curbp->b_undopos = find_dot(curwp->w_dotp, curwp->w_doto);