[BACK]Return to undo.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mg

Annotation of src/usr.bin/mg/undo.c, Revision 1.39

1.39    ! kjell       1: /* $OpenBSD: undo.c,v 1.38 2005/12/20 05:04:28 kjell Exp $ */
1.1       vincent     2: /*
1.25      db          3:  * Copyright (c) 2002 Vincent Labrecque <vincent@openbsd.org>
1.39    ! kjell       4:  * Copyright (c) 2005, 2006 Kjell Wooding <kjell@openbsd.org>
1.10      vincent     5:  * All rights reserved.
1.1       vincent     6:  *
                      7:  * Redistribution and use in source and binary forms, with or without
                      8:  * modification, are permitted provided that the following conditions
                      9:  * are met:
                     10:  * 1. Redistributions of source code must retain the above copyright
                     11:  *    notice, this list of conditions and the following disclaimer.
                     12:  * 2. Redistributions in binary form must reproduce the above copyright
                     13:  *    notice, this list of conditions and the following disclaimer in the
                     14:  *    documentation and/or other materials provided with the distribution.
                     15:  *
                     16:  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
                     17:  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
                     18:  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
                     19:  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
                     20:  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
                     21:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
                     22:  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
                     23:  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
                     24:  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
                     25:  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     26:  */
                     27:
                     28: #include "def.h"
                     29: #include "kbd.h"
                     30:
                     31: #define MAX_FREE_RECORDS       32
                     32:
                     33: /*
                     34:  * Local variables
                     35:  */
1.7       vincent    36: static LIST_HEAD(, undo_rec)    undo_free;
                     37: static int                      undo_free_num;
1.12      vincent    38: static int                      nobound;
1.1       vincent    39:
                     40: /*
                     41:  * Global variables
                     42:  */
                     43: /*
1.9       vincent    44:  * undo_disable_flag: Stop doing undo (useful when we know are
                     45:  *     going to deal with huge deletion/insertions
                     46:  *     that we don't plan to undo)
1.1       vincent    47:  */
                     48: int undo_disable_flag;
                     49:
                     50: /*
                     51:  * Local functions
                     52:  */
1.35      deraadt    53: static int find_dot(struct line *, int);
1.39    ! kjell      54: static int find_lo(int, struct line **, int *, int *);
1.1       vincent    55: static struct undo_rec *new_undo_record(void);
                     56: static int drop_oldest_undo_record(void);
                     57:
1.9       vincent    58: /*
1.24      vincent    59:  * find_dot, find_lo()
1.9       vincent    60:  *
                     61:  * Find an absolute dot in the buffer from a line/offset pair, and vice-versa.
                     62:  *
1.13      deraadt    63:  * Since lines can be deleted while they are referenced by undo record, we
1.9       vincent    64:  * need to have an absolute dot to have something reliable.
                     65:  */
1.1       vincent    66: static int
1.35      deraadt    67: find_dot(struct line *lp, int off)
1.1       vincent    68: {
1.25      db         69:        int      count = 0;
1.35      deraadt    70:        struct line     *p;
1.1       vincent    71:
1.21      vincent    72:        for (p = curbp->b_linep; p != lp; p = lforw(p)) {
1.1       vincent    73:                if (count != 0) {
1.21      vincent    74:                        if (p == curbp->b_linep) {
1.1       vincent    75:                                ewprintf("Error: Undo stuff called with a"
1.9       vincent    76:                                    "nonexistent line");
1.11      vincent    77:                                return (FALSE);
1.1       vincent    78:                        }
                     79:                }
                     80:                count += llength(p) + 1;
                     81:        }
                     82:        count += off;
                     83:
1.11      vincent    84:        return (count);
1.1       vincent    85: }
                     86:
                     87: static int
1.39    ! kjell      88: find_lo(int pos, struct line **olp, int *offset, int *lnum)
1.1       vincent    89: {
1.35      deraadt    90:        struct line *p;
1.39    ! kjell      91:        int lineno;
1.1       vincent    92:
1.21      vincent    93:        p = curbp->b_linep;
1.39    ! kjell      94:        lineno = 0;
1.9       vincent    95:        while (pos > llength(p)) {
1.1       vincent    96:                pos -= llength(p) + 1;
1.21      vincent    97:                if ((p = lforw(p)) == curbp->b_linep) {
1.1       vincent    98:                        *olp = NULL;
                     99:                        *offset = 0;
1.11      vincent   100:                        return (FALSE);
1.1       vincent   101:                }
1.39    ! kjell     102:                lineno++;
1.1       vincent   103:        }
                    104:        *olp = p;
                    105:        *offset = pos;
1.39    ! kjell     106:        *lnum = lineno;
1.1       vincent   107:
1.11      vincent   108:        return (TRUE);
1.1       vincent   109: }
                    110:
                    111: static struct undo_rec *
                    112: new_undo_record(void)
                    113: {
                    114:        struct undo_rec *rec;
                    115:
                    116:        rec = LIST_FIRST(&undo_free);
1.9       vincent   117:        if (rec != NULL) {
1.1       vincent   118:                LIST_REMOVE(rec, next); /* Remove it from the free-list */
1.9       vincent   119:                undo_free_num--;
                    120:        } else {
1.25      db        121:                if ((rec = malloc(sizeof(*rec))) == NULL)
1.1       vincent   122:                        panic("Out of memory in undo code (record)");
                    123:        }
                    124:        memset(rec, 0, sizeof(struct undo_rec));
1.9       vincent   125:
1.11      vincent   126:        return (rec);
1.1       vincent   127: }
                    128:
1.7       vincent   129: void
1.1       vincent   130: free_undo_record(struct undo_rec *rec)
                    131: {
1.9       vincent   132:        static int initialised = 0;
                    133:
                    134:        /*
                    135:         * On the first run, do initialisation of the free list.
                    136:         */
                    137:        if (initialised == 0) {
                    138:                LIST_INIT(&undo_free);
                    139:                initialised = 1;
                    140:        }
1.1       vincent   141:        if (rec->content != NULL) {
                    142:                free(rec->content);
                    143:                rec->content = NULL;
                    144:        }
                    145:        if (undo_free_num >= MAX_FREE_RECORDS) {
                    146:                free(rec);
                    147:                return;
                    148:        }
                    149:        undo_free_num++;
1.9       vincent   150:
1.1       vincent   151:        LIST_INSERT_HEAD(&undo_free, rec, next);
                    152: }
                    153:
                    154: /*
                    155:  * Drop the oldest undo record in our list. Return 1 if we could remove it,
1.25      db        156:  * 0 if the undo list was empty.
1.1       vincent   157:  */
                    158: static int
                    159: drop_oldest_undo_record(void)
                    160: {
                    161:        struct undo_rec *rec;
                    162:
1.29      kjell     163:        rec = LIST_END(&curbp->b_undo);
1.1       vincent   164:        if (rec != NULL) {
                    165:                undo_free_num--;
1.9       vincent   166:                LIST_REMOVE(rec, next);
1.1       vincent   167:                free_undo_record(rec);
1.11      vincent   168:                return (1);
1.1       vincent   169:        }
1.11      vincent   170:        return (0);
1.1       vincent   171: }
1.9       vincent   172:
                    173: static __inline__ int
1.22      vincent   174: lastrectype(void)
1.1       vincent   175: {
1.9       vincent   176:        struct undo_rec *rec;
                    177:
1.29      kjell     178:        if ((rec = LIST_FIRST(&curbp->b_undo)) != NULL)
1.22      vincent   179:                return (rec->type);
1.11      vincent   180:        return (0);
1.1       vincent   181: }
                    182:
1.30      kjell     183: /*
                    184:  * undo_enable(TRUE/FALSE) will enable / disable the undo mechanism.
                    185:  * Returns TRUE if previously enabled, FALSE otherwise.
                    186:  */
1.1       vincent   187: int
                    188: undo_enable(int on)
                    189: {
1.14      vincent   190:        int pon = undo_disable_flag;
1.22      vincent   191:
1.14      vincent   192:        undo_disable_flag = (on == TRUE) ? 0 : 1;
1.30      kjell     193:        return ((pon == TRUE) ? FALSE : TRUE);
1.5       vincent   194: }
                    195:
1.30      kjell     196: /*
                    197:  * If undo is enabled, then:
                    198:  *   undo_no_boundary(TRUE) stops recording undo boundaries between actions.
                    199:  *   undo_no_boundary(FALSE) enables undo boundaries.
                    200:  * If undo is disabled, this function has no effect.
                    201:  */
1.28      kjell     202: void
                    203: undo_no_boundary(int flag)
                    204: {
1.30      kjell     205:        if (undo_disable_flag == FALSE)
1.28      kjell     206:                nobound = flag;
                    207: }
                    208:
1.30      kjell     209: /*
                    210:  * Record an undo boundary, unless 'nobound' is set via undo_no_boundary.
                    211:  * Does nothing if previous undo entry is already a boundary.
                    212:  */
1.37      kjell     213: void
1.9       vincent   214: undo_add_boundary(void)
1.5       vincent   215: {
                    216:        struct undo_rec *rec;
                    217:
1.12      vincent   218:        if (nobound)
1.37      kjell     219:                return;
1.12      vincent   220:
1.28      kjell     221:        if (lastrectype() == BOUNDARY)
1.37      kjell     222:                return;
1.31      deraadt   223:
1.5       vincent   224:        rec = new_undo_record();
1.9       vincent   225:        rec->type = BOUNDARY;
1.5       vincent   226:
1.29      kjell     227:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.9       vincent   228:
1.37      kjell     229:        return;
1.1       vincent   230: }
                    231:
                    232: int
1.35      deraadt   233: undo_add_insert(struct line *lp, int offset, int size)
1.1       vincent   234: {
1.35      deraadt   235:        struct region   reg;
1.25      db        236:        struct  undo_rec *rec;
                    237:        int     pos;
1.9       vincent   238:
1.1       vincent   239:        if (undo_disable_flag)
1.11      vincent   240:                return (TRUE);
1.1       vincent   241:        reg.r_linep = lp;
                    242:        reg.r_offset = offset;
                    243:        reg.r_size = size;
1.9       vincent   244:
1.24      vincent   245:        pos = find_dot(lp, offset);
1.9       vincent   246:
1.1       vincent   247:        /*
                    248:         * We try to reuse the last undo record to `compress' things.
1.13      deraadt   249:         */
1.29      kjell     250:        rec = LIST_FIRST(&curbp->b_undo);
1.23      vincent   251:        if (rec != NULL && rec->type == INSERT) {
                    252:                if (rec->pos + rec->region.r_size == pos) {
                    253:                        rec->region.r_size += reg.r_size;
                    254:                        return (TRUE);
1.1       vincent   255:                }
                    256:        }
1.9       vincent   257:
1.1       vincent   258:        /*
1.25      db        259:         * We couldn't reuse the last undo record, so prepare a new one.
1.1       vincent   260:         */
                    261:        rec = new_undo_record();
1.9       vincent   262:        rec->pos = pos;
1.1       vincent   263:        rec->type = INSERT;
1.35      deraadt   264:        memmove(&rec->region, &reg, sizeof(struct region));
1.1       vincent   265:        rec->content = NULL;
1.9       vincent   266:
1.27      cloder    267:        undo_add_boundary();
1.9       vincent   268:
1.29      kjell     269:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1       vincent   270:
1.11      vincent   271:        return (TRUE);
1.1       vincent   272: }
                    273:
                    274: /*
1.25      db        275:  * This of course must be done _before_ the actual deletion is done.
1.1       vincent   276:  */
                    277: int
1.35      deraadt   278: undo_add_delete(struct line *lp, int offset, int size)
1.1       vincent   279: {
1.35      deraadt   280:        struct region   reg;
1.25      db        281:        struct  undo_rec *rec;
                    282:        int     pos;
1.1       vincent   283:
                    284:        if (undo_disable_flag)
1.11      vincent   285:                return (TRUE);
1.1       vincent   286:
                    287:        reg.r_linep = lp;
                    288:        reg.r_offset = offset;
                    289:        reg.r_size = size;
                    290:
1.24      vincent   291:        pos = find_dot(lp, offset);
1.3       vincent   292:
1.10      vincent   293:        if (offset == llength(lp))      /* if it's a newline... */
1.9       vincent   294:                undo_add_boundary();
1.29      kjell     295:        else if ((rec = LIST_FIRST(&curbp->b_undo)) != NULL) {
1.9       vincent   296:                /*
                    297:                 * Separate this command from the previous one if we're not
                    298:                 * just before the previous record...
                    299:                 */
1.10      vincent   300:                if (rec->type == DELETE) {
1.9       vincent   301:                        if (rec->pos - rec->region.r_size != pos)
                    302:                                undo_add_boundary();
1.23      vincent   303:                }
1.3       vincent   304:        }
1.1       vincent   305:        rec = new_undo_record();
                    306:        rec->pos = pos;
                    307:
                    308:        rec->type = DELETE;
1.35      deraadt   309:        memmove(&rec->region, &reg, sizeof(struct region));
1.1       vincent   310:        do {
                    311:                rec->content = malloc(reg.r_size + 1);
                    312:        } while ((rec->content == NULL) && drop_oldest_undo_record());
1.9       vincent   313:
1.1       vincent   314:        if (rec->content == NULL)
                    315:                panic("Out of memory");
1.9       vincent   316:
1.1       vincent   317:        region_get_data(&reg, rec->content, reg.r_size);
                    318:
1.22      vincent   319:        if (lastrectype() != DELETE)
                    320:                undo_add_boundary();
                    321:
1.29      kjell     322:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1       vincent   323:
1.11      vincent   324:        return (TRUE);
1.1       vincent   325: }
                    326:
                    327: /*
1.25      db        328:  * This of course must be called before the change takes place.
1.1       vincent   329:  */
                    330: int
1.35      deraadt   331: undo_add_change(struct line *lp, int offset, int size)
1.1       vincent   332: {
                    333:        if (undo_disable_flag)
1.11      vincent   334:                return (TRUE);
1.12      vincent   335:        undo_add_boundary();
1.28      kjell     336:        nobound = TRUE;
1.12      vincent   337:        undo_add_delete(lp, offset, size);
                    338:        undo_add_insert(lp, offset, size);
1.28      kjell     339:        nobound = FALSE;
1.9       vincent   340:        undo_add_boundary();
                    341:
1.11      vincent   342:        return (TRUE);
1.8       vincent   343: }
                    344:
                    345: /*
                    346:  * Show the undo records for the current buffer in a new buffer.
                    347:  */
1.32      kjell     348: /* ARGSUSED */
1.8       vincent   349: int
1.18      vincent   350: undo_dump(int f, int n)
1.8       vincent   351: {
1.25      db        352:        struct   undo_rec *rec;
1.35      deraadt   353:        struct buffer   *bp;
                    354:        struct mgwin    *wp;
1.25      db        355:        char     buf[4096], tmp[1024];
                    356:        int      num;
1.8       vincent   357:
                    358:        /*
                    359:         * Prepare the buffer for insertion.
                    360:         */
                    361:        if ((bp = bfind("*undo*", TRUE)) == NULL)
1.11      vincent   362:                return (FALSE);
1.9       vincent   363:        bp->b_flag |= BFREADONLY;
1.8       vincent   364:        bclear(bp);
                    365:        popbuf(bp);
                    366:
1.10      vincent   367:        for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
1.8       vincent   368:                if (wp->w_bufp == bp) {
                    369:                        wp->w_dotp = bp->b_linep;
                    370:                        wp->w_doto = 0;
                    371:                }
1.10      vincent   372:        }
1.8       vincent   373:
                    374:        num = 0;
1.29      kjell     375:        for (rec = LIST_FIRST(&curbp->b_undo); rec != NULL;
1.13      deraadt   376:            rec = LIST_NEXT(rec, next)) {
1.8       vincent   377:                num++;
1.25      db        378:                snprintf(buf, sizeof(buf),
1.38      kjell     379:                    "%d:\t %s at %d ", num,
1.8       vincent   380:                    (rec->type == DELETE) ? "DELETE":
                    381:                    (rec->type == INSERT) ? "INSERT":
                    382:                    (rec->type == BOUNDARY) ? "----" : "UNKNOWN",
                    383:                    rec->pos);
1.10      vincent   384:
1.12      vincent   385:                if (rec->content) {
1.38      kjell     386:                        (void)strlcat(buf, "\"", sizeof(buf));
1.25      db        387:                        snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
1.8       vincent   388:                            rec->content);
1.38      kjell     389:                        (void)strlcat(buf, tmp, sizeof(buf));
                    390:                        (void)strlcat(buf, "\"", sizeof(buf));
1.8       vincent   391:                }
1.25      db        392:                snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
1.33      kjell     393:                if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) {
                    394:                        ewprintf("Undo record too large. Aborted.");
                    395:                        return (FALSE);
                    396:                }
1.17      deraadt   397:                addlinef(bp, "%s", buf);
1.8       vincent   398:        }
1.11      vincent   399:        return (TRUE);
1.1       vincent   400: }
                    401:
1.9       vincent   402: /*
1.25      db        403:  * After the user did action1, then action2, then action3:
1.9       vincent   404:  *
                    405:  *     [action3] <--- Undoptr
                    406:  *     [action2]
                    407:  *     [action1]
                    408:  *      ------
                    409:  *      [undo]
                    410:  *
                    411:  * After undo:
                    412:  *
                    413:  *     [undo of action3]
                    414:  *     [action2] <--- Undoptr
                    415:  *     [action1]
                    416:  *      ------
                    417:  *      [undo]
1.13      deraadt   418:  *
1.9       vincent   419:  * After another undo:
                    420:  *
                    421:  *
                    422:  *     [undo of action2]
                    423:  *     [undo of action3]
                    424:  *     [action1]  <--- Undoptr
                    425:  *      ------
                    426:  *      [undo]
1.13      deraadt   427:  *
1.25      db        428:  * Note that the "undo of actionX" have no special meaning. Only when
                    429:  * we undo a deletion, the insertion will be recorded just as if it
1.10      vincent   430:  * was typed on the keyboard. Resulting in the inverse operation being
1.9       vincent   431:  * saved in the list.
                    432:  *
                    433:  * If undoptr reaches the bottom of the list, or if we moved between
                    434:  * two undo actions, we make it point back at the topmost record. This is
                    435:  * how we handle redoing.
                    436:  */
1.32      kjell     437: /* ARGSUSED */
1.1       vincent   438: int
1.4       vincent   439: undo(int f, int n)
1.1       vincent   440: {
1.25      db        441:        struct undo_rec *ptr, *nptr;
1.31      deraadt   442:        int              done, rval;
1.36      kjell     443:        struct line     *lp;
1.25      db        444:        int              offset, save, dot;
1.28      kjell     445:        static int       nulled = FALSE;
1.39    ! kjell     446:        int              lineno;
1.23      vincent   447:
1.24      vincent   448:        dot = find_dot(curwp->w_dotp, curwp->w_doto);
1.9       vincent   449:
1.29      kjell     450:        ptr = curbp->b_undoptr;
1.9       vincent   451:
                    452:        /* if we moved, make ptr point back to the top of the list */
1.29      kjell     453:        if ((ptr == NULL && nulled == TRUE) || curbp->b_undopos != dot) {
                    454:                ptr = LIST_FIRST(&curbp->b_undo);
1.28      kjell     455:                nulled = TRUE;
                    456:        }
1.1       vincent   457:
1.9       vincent   458:        rval = TRUE;
1.10      vincent   459:        while (n--) {
1.9       vincent   460:                /* if we have a spurious boundary, free it and move on.... */
                    461:                while (ptr && ptr->type == BOUNDARY) {
                    462:                        nptr = LIST_NEXT(ptr, next);
                    463:                        LIST_REMOVE(ptr, next);
                    464:                        free_undo_record(ptr);
                    465:                        ptr = nptr;
1.4       vincent   466:                }
                    467:                /*
1.9       vincent   468:                 * Ptr is NULL, but on the next run, it will point to the
                    469:                 * top again, redoing all stuff done in the buffer since
                    470:                 * its creation.
1.4       vincent   471:                 */
1.9       vincent   472:                if (ptr == NULL) {
1.11      vincent   473:                        ewprintf("No further undo information");
1.9       vincent   474:                        rval = FALSE;
1.28      kjell     475:                        nulled = TRUE;
1.4       vincent   476:                        break;
                    477:                }
1.28      kjell     478:                nulled = FALSE;
1.6       vincent   479:
1.13      deraadt   480:                /*
1.9       vincent   481:                 * Loop while we don't get a boundary specifying we've
                    482:                 * finished the current action...
                    483:                 */
1.22      vincent   484:
1.28      kjell     485:                undo_add_boundary();
1.22      vincent   486:
                    487:                save = nobound;
1.28      kjell     488:                nobound = TRUE;
1.22      vincent   489:
1.9       vincent   490:                done = 0;
                    491:                do {
                    492:                        /*
                    493:                         * Move to where this has to apply
                    494:                         *
                    495:                         * Boundaries are put as position 0 (to save
1.24      vincent   496:                         * lookup time in find_dot) so we must
1.9       vincent   497:                         * not move there...
                    498:                         */
                    499:                        if (ptr->type != BOUNDARY) {
1.24      vincent   500:                                if (find_lo(ptr->pos, &lp,
1.39    ! kjell     501:                                    &offset, &lineno) == FALSE) {
1.9       vincent   502:                                        ewprintf("Internal error in Undo!");
                    503:                                        rval = FALSE;
                    504:                                        break;
                    505:                                }
                    506:                                curwp->w_dotp = lp;
                    507:                                curwp->w_doto = offset;
1.39    ! kjell     508:                                curwp->w_markline = curwp->w_dotline;
        !           509:                                curwp->w_dotline = lineno;
1.9       vincent   510:                        }
                    511:
                    512:                        /*
                    513:                         * Do operation^-1
                    514:                         */
                    515:                        switch (ptr->type) {
                    516:                        case INSERT:
1.34      kjell     517:                                ldelete(ptr->region.r_size, KNONE);
1.9       vincent   518:                                break;
                    519:                        case DELETE:
1.13      deraadt   520:                                region_put_data(ptr->content,
1.9       vincent   521:                                    ptr->region.r_size);
                    522:                                break;
                    523:                        case BOUNDARY:
                    524:                                done = 1;
                    525:                                break;
                    526:                        default:
                    527:                                break;
                    528:                        }
                    529:
                    530:                        /* And move to next record */
1.22      vincent   531:                        ptr = LIST_NEXT(ptr, next);
1.9       vincent   532:                } while (ptr != NULL && !done);
1.22      vincent   533:
                    534:                nobound = save;
                    535:                undo_add_boundary();
1.9       vincent   536:
                    537:                ewprintf("Undo!");
1.1       vincent   538:        }
1.9       vincent   539:        /*
                    540:         * Record where we are. (we have to save our new position at the end
                    541:         * since we change the dot when undoing....)
                    542:         */
1.29      kjell     543:        curbp->b_undoptr = ptr;
1.23      vincent   544:
1.29      kjell     545:        curbp->b_undopos = find_dot(curwp->w_dotp, curwp->w_doto);
1.9       vincent   546:
1.13      deraadt   547:        return (rval);
1.1       vincent   548: }