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

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