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

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