[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.35

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