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

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