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

1.28    ! kjell       1: /* $OpenBSD: undo.c,v 1.27 2005/05/29 21:37:49 cloder 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.24      vincent    52: static int find_dot(LINE *, int);
                     53: static int find_lo(int, 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.24      vincent    66: find_dot(LINE *lp, int off)
1.1       vincent    67: {
1.25      db         68:        int      count = 0;
                     69:        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.24      vincent    87: find_lo(int pos, LINE **olp, int *offset)
1.1       vincent    88: {
                     89:        LINE *p;
                     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.19      vincent   158:        rec = LIST_END(&curwp->w_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.22      vincent   173:        if ((rec = LIST_FIRST(&curwp->w_undo)) != NULL)
                    174:                return (rec->type);
1.11      vincent   175:        return (0);
1.1       vincent   176: }
                    177:
                    178: int
                    179: undo_enable(int on)
                    180: {
1.14      vincent   181:        int pon = undo_disable_flag;
1.22      vincent   182:
1.14      vincent   183:        undo_disable_flag = (on == TRUE) ? 0 : 1;
                    184:        return (pon ? FALSE : TRUE);
1.5       vincent   185: }
                    186:
1.28    ! kjell     187: void
        !           188: undo_no_boundary(int flag)
        !           189: {
        !           190:        if (!undo_disable_flag)
        !           191:                nobound = flag;
        !           192: }
        !           193:
1.5       vincent   194: int
1.9       vincent   195: undo_add_boundary(void)
1.5       vincent   196: {
                    197:        struct undo_rec *rec;
                    198:
1.12      vincent   199:        if (nobound)
                    200:                return (TRUE);
                    201:
1.28    ! kjell     202:        if (lastrectype() == BOUNDARY)
        !           203:                return (TRUE);
        !           204:
1.5       vincent   205:        rec = new_undo_record();
1.9       vincent   206:        rec->type = BOUNDARY;
1.5       vincent   207:
1.19      vincent   208:        LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.9       vincent   209:
1.11      vincent   210:        return (TRUE);
1.1       vincent   211: }
                    212:
                    213: int
                    214: undo_add_insert(LINE *lp, int offset, int size)
                    215: {
1.25      db        216:        REGION  reg;
                    217:        struct  undo_rec *rec;
                    218:        int     pos;
1.9       vincent   219:
1.1       vincent   220:        if (undo_disable_flag)
1.11      vincent   221:                return (TRUE);
1.1       vincent   222:        reg.r_linep = lp;
                    223:        reg.r_offset = offset;
                    224:        reg.r_size = size;
1.9       vincent   225:
1.24      vincent   226:        pos = find_dot(lp, offset);
1.9       vincent   227:
1.1       vincent   228:        /*
                    229:         * We try to reuse the last undo record to `compress' things.
1.13      deraadt   230:         */
1.19      vincent   231:        rec = LIST_FIRST(&curwp->w_undo);
1.23      vincent   232:        if (rec != NULL && rec->type == INSERT) {
                    233:                if (rec->pos + rec->region.r_size == pos) {
                    234:                        rec->region.r_size += reg.r_size;
                    235:                        return (TRUE);
1.1       vincent   236:                }
                    237:        }
1.9       vincent   238:
1.1       vincent   239:        /*
1.25      db        240:         * We couldn't reuse the last undo record, so prepare a new one.
1.1       vincent   241:         */
                    242:        rec = new_undo_record();
1.9       vincent   243:        rec->pos = pos;
1.1       vincent   244:        rec->type = INSERT;
                    245:        memmove(&rec->region, &reg, sizeof(REGION));
                    246:        rec->content = NULL;
1.9       vincent   247:
1.27      cloder    248:        undo_add_boundary();
1.9       vincent   249:
1.19      vincent   250:        LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.1       vincent   251:
1.11      vincent   252:        return (TRUE);
1.1       vincent   253: }
                    254:
                    255: /*
1.25      db        256:  * This of course must be done _before_ the actual deletion is done.
1.1       vincent   257:  */
                    258: int
                    259: undo_add_delete(LINE *lp, int offset, int size)
                    260: {
1.25      db        261:        REGION  reg;
                    262:        struct  undo_rec *rec;
                    263:        int     pos;
1.1       vincent   264:
                    265:        if (undo_disable_flag)
1.11      vincent   266:                return (TRUE);
1.1       vincent   267:
                    268:        reg.r_linep = lp;
                    269:        reg.r_offset = offset;
                    270:        reg.r_size = size;
                    271:
1.24      vincent   272:        pos = find_dot(lp, offset);
1.3       vincent   273:
1.10      vincent   274:        if (offset == llength(lp))      /* if it's a newline... */
1.9       vincent   275:                undo_add_boundary();
1.19      vincent   276:        else if ((rec = LIST_FIRST(&curwp->w_undo)) != NULL) {
1.9       vincent   277:                /*
                    278:                 * Separate this command from the previous one if we're not
                    279:                 * just before the previous record...
                    280:                 */
1.10      vincent   281:                if (rec->type == DELETE) {
1.9       vincent   282:                        if (rec->pos - rec->region.r_size != pos)
                    283:                                undo_add_boundary();
1.23      vincent   284:                }
1.3       vincent   285:        }
1.1       vincent   286:        rec = new_undo_record();
                    287:        rec->pos = pos;
                    288:
                    289:        rec->type = DELETE;
                    290:        memmove(&rec->region, &reg, sizeof(REGION));
                    291:        do {
                    292:                rec->content = malloc(reg.r_size + 1);
                    293:        } while ((rec->content == NULL) && drop_oldest_undo_record());
1.9       vincent   294:
1.1       vincent   295:        if (rec->content == NULL)
                    296:                panic("Out of memory");
1.9       vincent   297:
1.1       vincent   298:        region_get_data(&reg, rec->content, reg.r_size);
                    299:
1.22      vincent   300:        if (lastrectype() != DELETE)
                    301:                undo_add_boundary();
                    302:
1.19      vincent   303:        LIST_INSERT_HEAD(&curwp->w_undo, rec, next);
1.1       vincent   304:
1.11      vincent   305:        return (TRUE);
1.1       vincent   306: }
                    307:
                    308: /*
1.25      db        309:  * This of course must be called before the change takes place.
1.1       vincent   310:  */
                    311: int
                    312: undo_add_change(LINE *lp, int offset, int size)
                    313: {
                    314:        if (undo_disable_flag)
1.11      vincent   315:                return (TRUE);
1.12      vincent   316:        undo_add_boundary();
1.28    ! kjell     317:        nobound = TRUE;
1.12      vincent   318:        undo_add_delete(lp, offset, size);
                    319:        undo_add_insert(lp, offset, size);
1.28    ! kjell     320:        nobound = FALSE;
1.9       vincent   321:        undo_add_boundary();
                    322:
1.11      vincent   323:        return (TRUE);
1.8       vincent   324: }
                    325:
                    326: /*
                    327:  * Show the undo records for the current buffer in a new buffer.
                    328:  */
                    329: int
1.18      vincent   330: undo_dump(int f, int n)
1.8       vincent   331: {
1.25      db        332:        struct   undo_rec *rec;
                    333:        BUFFER  *bp;
                    334:        MGWIN   *wp;
                    335:        char     buf[4096], tmp[1024];
                    336:        int      num;
1.8       vincent   337:
                    338:        /*
                    339:         * Prepare the buffer for insertion.
                    340:         */
                    341:        if ((bp = bfind("*undo*", TRUE)) == NULL)
1.11      vincent   342:                return (FALSE);
1.9       vincent   343:        bp->b_flag |= BFREADONLY;
1.8       vincent   344:        bclear(bp);
                    345:        popbuf(bp);
                    346:
1.10      vincent   347:        for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
1.8       vincent   348:                if (wp->w_bufp == bp) {
                    349:                        wp->w_dotp = bp->b_linep;
                    350:                        wp->w_doto = 0;
                    351:                }
1.10      vincent   352:        }
1.8       vincent   353:
                    354:        num = 0;
1.19      vincent   355:        for (rec = LIST_FIRST(&curwp->w_undo); rec != NULL;
1.13      deraadt   356:            rec = LIST_NEXT(rec, next)) {
1.8       vincent   357:                num++;
1.25      db        358:                snprintf(buf, sizeof(buf),
1.8       vincent   359:                    "Record %d =>\t %s at %d ", num,
                    360:                    (rec->type == DELETE) ? "DELETE":
                    361:                    (rec->type == INSERT) ? "INSERT":
                    362:                    (rec->type == BOUNDARY) ? "----" : "UNKNOWN",
                    363:                    rec->pos);
1.10      vincent   364:
1.12      vincent   365:                if (rec->content) {
1.25      db        366:                        strlcat(buf, "\"", sizeof(buf));
                    367:                        snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
1.8       vincent   368:                            rec->content);
1.25      db        369:                        strlcat(buf, tmp, sizeof(buf));
                    370:                        strlcat(buf, "\"", sizeof(buf));
1.8       vincent   371:                }
1.25      db        372:                snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
                    373:                strlcat(buf, tmp, sizeof(buf));
1.17      deraadt   374:                addlinef(bp, "%s", buf);
1.8       vincent   375:        }
1.11      vincent   376:        return (TRUE);
1.1       vincent   377: }
                    378:
1.9       vincent   379: /*
1.25      db        380:  * After the user did action1, then action2, then action3:
1.9       vincent   381:  *
                    382:  *     [action3] <--- Undoptr
                    383:  *     [action2]
                    384:  *     [action1]
                    385:  *      ------
                    386:  *      [undo]
                    387:  *
                    388:  * After undo:
                    389:  *
                    390:  *     [undo of action3]
                    391:  *     [action2] <--- Undoptr
                    392:  *     [action1]
                    393:  *      ------
                    394:  *      [undo]
1.13      deraadt   395:  *
1.9       vincent   396:  * After another undo:
                    397:  *
                    398:  *
                    399:  *     [undo of action2]
                    400:  *     [undo of action3]
                    401:  *     [action1]  <--- Undoptr
                    402:  *      ------
                    403:  *      [undo]
1.13      deraadt   404:  *
1.25      db        405:  * Note that the "undo of actionX" have no special meaning. Only when
                    406:  * we undo a deletion, the insertion will be recorded just as if it
1.10      vincent   407:  * was typed on the keyboard. Resulting in the inverse operation being
1.9       vincent   408:  * saved in the list.
                    409:  *
                    410:  * If undoptr reaches the bottom of the list, or if we moved between
                    411:  * two undo actions, we make it point back at the topmost record. This is
                    412:  * how we handle redoing.
                    413:  */
1.1       vincent   414: int
1.4       vincent   415: undo(int f, int n)
1.1       vincent   416: {
1.25      db        417:        struct undo_rec *ptr, *nptr;
                    418:        int              done, rval;
                    419:        LINE            *lp;
                    420:        int              offset, save, dot;
1.28    ! kjell     421:        static int       nulled = FALSE;
1.23      vincent   422:
1.24      vincent   423:        dot = find_dot(curwp->w_dotp, curwp->w_doto);
1.9       vincent   424:
1.19      vincent   425:        ptr = curwp->w_undoptr;
1.9       vincent   426:
                    427:        /* if we moved, make ptr point back to the top of the list */
1.28    ! kjell     428:        if ((ptr == NULL && nulled == TRUE) || curwp->w_undopos != dot) {
1.19      vincent   429:                ptr = LIST_FIRST(&curwp->w_undo);
1.28    ! kjell     430:                nulled = TRUE;
        !           431:        }
1.1       vincent   432:
1.9       vincent   433:        rval = TRUE;
1.10      vincent   434:        while (n--) {
1.9       vincent   435:                /* if we have a spurious boundary, free it and move on.... */
                    436:                while (ptr && ptr->type == BOUNDARY) {
                    437:                        nptr = LIST_NEXT(ptr, next);
                    438:                        LIST_REMOVE(ptr, next);
                    439:                        free_undo_record(ptr);
                    440:                        ptr = nptr;
1.4       vincent   441:                }
                    442:                /*
1.9       vincent   443:                 * Ptr is NULL, but on the next run, it will point to the
                    444:                 * top again, redoing all stuff done in the buffer since
                    445:                 * its creation.
1.4       vincent   446:                 */
1.9       vincent   447:                if (ptr == NULL) {
1.11      vincent   448:                        ewprintf("No further undo information");
1.9       vincent   449:                        rval = FALSE;
1.28    ! kjell     450:                        nulled = TRUE;
1.4       vincent   451:                        break;
                    452:                }
1.28    ! kjell     453:                nulled = FALSE;
1.6       vincent   454:
1.13      deraadt   455:                /*
1.9       vincent   456:                 * Loop while we don't get a boundary specifying we've
                    457:                 * finished the current action...
                    458:                 */
1.22      vincent   459:
1.28    ! kjell     460:                undo_add_boundary();
1.22      vincent   461:
                    462:                save = nobound;
1.28    ! kjell     463:                nobound = TRUE;
1.22      vincent   464:
1.9       vincent   465:                done = 0;
                    466:                do {
                    467:                        /*
                    468:                         * Move to where this has to apply
                    469:                         *
                    470:                         * Boundaries are put as position 0 (to save
1.24      vincent   471:                         * lookup time in find_dot) so we must
1.9       vincent   472:                         * not move there...
                    473:                         */
                    474:                        if (ptr->type != BOUNDARY) {
1.24      vincent   475:                                if (find_lo(ptr->pos, &lp,
1.10      vincent   476:                                    &offset) == FALSE) {
1.9       vincent   477:                                        ewprintf("Internal error in Undo!");
                    478:                                        rval = FALSE;
                    479:                                        break;
                    480:                                }
                    481:                                curwp->w_dotp = lp;
                    482:                                curwp->w_doto = offset;
                    483:                        }
                    484:
                    485:                        /*
                    486:                         * Do operation^-1
                    487:                         */
                    488:                        switch (ptr->type) {
                    489:                        case INSERT:
                    490:                                ldelete(ptr->region.r_size, KFORW);
                    491:                                break;
                    492:                        case DELETE:
1.13      deraadt   493:                                region_put_data(ptr->content,
1.9       vincent   494:                                    ptr->region.r_size);
                    495:                                break;
                    496:                        case BOUNDARY:
                    497:                                done = 1;
                    498:                                break;
                    499:                        default:
                    500:                                break;
                    501:                        }
                    502:
                    503:                        /* And move to next record */
1.22      vincent   504:                        ptr = LIST_NEXT(ptr, next);
1.9       vincent   505:                } while (ptr != NULL && !done);
1.22      vincent   506:
                    507:                nobound = save;
                    508:                undo_add_boundary();
1.9       vincent   509:
                    510:                ewprintf("Undo!");
1.1       vincent   511:        }
1.9       vincent   512:        /*
                    513:         * Record where we are. (we have to save our new position at the end
                    514:         * since we change the dot when undoing....)
                    515:         */
1.19      vincent   516:        curwp->w_undoptr = ptr;
1.23      vincent   517:
1.24      vincent   518:        curwp->w_undopos = find_dot(curwp->w_dotp, curwp->w_doto);
1.9       vincent   519:
1.13      deraadt   520:        return (rval);
1.1       vincent   521: }