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

1.7     ! vincent     1: /* $OpenBSD: undo.c,v 1.6 2002/02/21 17:36:12 vincent Exp $ */
1.1       vincent     2: /*
                      3:  * Copyright (c) 2002 Vincent Labrecque <vincent@openbsd.org>
                      4:  *                                                      All rights reserved.
                      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.1       vincent    39:
                     40: /*
                     41:  * Global variables
                     42:  */
                     43: /*
                     44:  * undo_disable_flag -
                     45:  *
                     46:  * Stop doing undo (useful when we know are
                     47:  * going to deal with huge deletion/insertions
                     48:  * that we don't plan to undo)
                     49:  */
                     50: int undo_disable_flag;
                     51: int undoaction;                        /* Are we called indirectly from undo()? */
                     52:
                     53: /*
                     54:  * Local functions
                     55:  */
                     56: static int find_offset(LINE *, int);
                     57: static int find_linep(int, LINE **, int *);
                     58: static struct undo_rec *new_undo_record(void);
                     59: static int drop_oldest_undo_record(void);
                     60:
                     61: static int
                     62: find_offset(LINE *lp, int off)
                     63: {
                     64:        int count = 0;
                     65:        LINE *p;
                     66:
                     67:        for (p = curwp->w_linep; p != lp; p = lforw(p)) {
                     68:                if (count != 0) {
1.3       vincent    69:                        if (p == curwp->w_linep) {
1.1       vincent    70:                                ewprintf("Error: Undo stuff called with a"
                     71:                                    "nonexistent line\n");
                     72:                                return FALSE;
                     73:                        }
                     74:                }
                     75:                count += llength(p) + 1;
                     76:        }
                     77:        count += off;
                     78:
                     79:        return count;
                     80: }
                     81:
                     82: static int
                     83: find_linep(int pos, LINE **olp, int *offset)
                     84: {
                     85:        LINE *p;
                     86:
                     87:        p = curwp->w_linep;
                     88:        while (pos > 0 && pos > llength(p)) {
                     89:                pos -= llength(p) + 1;
1.2       vincent    90:                if ((p = lforw(p)) == curwp->w_linep) {
1.1       vincent    91:                        *olp = NULL;
                     92:                        *offset = 0;
                     93:                        return FALSE;
                     94:                }
                     95:        }
                     96:        *olp = p;
                     97:        *offset = pos;
                     98:
                     99:        return TRUE;
                    100: }
                    101:
                    102: static struct undo_rec *
                    103: new_undo_record(void)
                    104: {
                    105:        struct undo_rec *rec;
                    106:
                    107:        rec = LIST_FIRST(&undo_free);
                    108:        if (rec != NULL)
                    109:                LIST_REMOVE(rec, next); /* Remove it from the free-list */
                    110:        else {
                    111:                if ((rec = malloc(sizeof *rec)) == NULL)
                    112:                        panic("Out of memory in undo code (record)");
                    113:        }
                    114:        memset(rec, 0, sizeof(struct undo_rec));
                    115:
                    116:        return rec;
                    117: }
                    118:
1.7     ! vincent   119: void
1.1       vincent   120: free_undo_record(struct undo_rec *rec)
                    121: {
                    122:        if (rec->content != NULL) {
                    123:                free(rec->content);
                    124:                rec->content = NULL;
                    125:        }
                    126:        if (undo_free_num >= MAX_FREE_RECORDS) {
                    127:                free(rec);
                    128:                return;
                    129:        }
                    130:        undo_free_num++;
                    131:
                    132:        LIST_INSERT_HEAD(&undo_free, rec, next);
                    133: }
                    134:
                    135: /*
                    136:  * Drop the oldest undo record in our list. Return 1 if we could remove it,
                    137:  * 0 if the undo list was empty
                    138:  */
                    139: static int
                    140: drop_oldest_undo_record(void)
                    141: {
                    142:        struct undo_rec *rec;
                    143:
                    144:        rec = LIST_END(&undo_list);
                    145:        if (rec != NULL) {
                    146:                undo_free_num--;
                    147:                LIST_REMOVE(rec, next); /* Remove it from the undo_list before
                    148:                                         * we insert it in the free list */
                    149:                free_undo_record(rec);
                    150:                return 1;
                    151:        }
                    152:        return 0;
                    153: }
                    154:
                    155: int
                    156: undo_init(void)
                    157: {
                    158:        LIST_INIT(&undo_free);
                    159:
                    160:        return TRUE;
                    161: }
                    162:
                    163: int
                    164: undo_enable(int on)
                    165: {
                    166:        undo_disable_flag = on ? 0 : 1;
                    167:
                    168:        /*
                    169:         * XXX-Vince:
                    170:         *
                    171:         * Here, I wonder if we should assume that the user has made a
                    172:         * long term choice.  If so, we could free all internal undo
                    173:         * data and save memory.
                    174:         */
                    175:
                    176:        return on;
1.5       vincent   177: }
                    178:
                    179: int
                    180: undo_add_custom(int type, LINE *lp, int offset, void *content, int size)
                    181: {
                    182:        struct undo_rec *rec;
                    183:
                    184:        if (undo_disable_flag)
                    185:                return TRUE;
                    186:        rec = new_undo_record();
                    187:        rec->pos = find_offset(lp, offset);
                    188:        rec->type = type;
                    189:        rec->content = content;
                    190:        rec->region.r_linep = lp;
                    191:        rec->region.r_offset = offset;
                    192:        rec->region.r_size = size;
                    193:
1.7     ! vincent   194:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.5       vincent   195:
                    196:        return TRUE;
1.1       vincent   197: }
                    198:
                    199: int
                    200: undo_add_boundary(void)
                    201: {
                    202:        struct undo_rec *rec;
                    203:
                    204:        if (undo_disable_flag)
                    205:                return TRUE;
                    206:
                    207:        rec = new_undo_record();
                    208:        rec->type = BOUNDARY;
                    209:
1.7     ! vincent   210:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1       vincent   211:
                    212:        return TRUE;
                    213: }
                    214:
                    215: int
                    216: undo_add_insert(LINE *lp, int offset, int size)
                    217: {
                    218:        REGION reg;
                    219:        struct undo_rec *rec;
                    220:
                    221:        if (undo_disable_flag)
                    222:                return TRUE;
                    223:
                    224:        reg.r_linep = lp;
                    225:        reg.r_offset = offset;
                    226:        reg.r_size = size;
                    227:
                    228:        /*
                    229:         * We try to reuse the last undo record to `compress' things.
                    230:         */
1.7     ! vincent   231:        rec = LIST_FIRST(&curbp->b_undo);
1.1       vincent   232:        if ((rec != NULL) &&
                    233:            (rec->type == INSERT) &&
                    234:            (rec->region.r_linep == lp)) {
                    235:                int dist;
                    236:
                    237:                dist = rec->region.r_offset - reg.r_offset;
                    238:
                    239:                if (rec->region.r_size >= dist) {
                    240:                        rec->region.r_size += reg.r_size;
                    241:                        return TRUE;
                    242:                }
                    243:        }
1.6       vincent   244:
1.1       vincent   245:        /*
                    246:         * We couldn't reuse the last undo record, so prepare a new one
                    247:         */
                    248:        rec = new_undo_record();
                    249:        rec->pos = find_offset(lp, offset);
                    250:        rec->type = INSERT;
                    251:        memmove(&rec->region, &reg, sizeof(REGION));
                    252:        rec->content = NULL;
1.7     ! vincent   253:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1       vincent   254:
                    255:        return TRUE;
                    256: }
                    257:
                    258: /*
                    259:  * This of course must be done _before_ the actual deletion is done
                    260:  */
                    261: int
                    262: undo_add_delete(LINE *lp, int offset, int size)
                    263: {
                    264:        REGION reg;
                    265:        struct undo_rec *rec;
1.2       vincent   266:        int dist, pos, skip = 0;
1.1       vincent   267:
                    268:        if (undo_disable_flag)
                    269:                return TRUE;
                    270:
                    271:        reg.r_linep = lp;
                    272:        reg.r_offset = offset;
                    273:        reg.r_size = size;
                    274:
                    275:        pos = find_offset(lp, offset);
1.3       vincent   276:
                    277:        if (size == 1 && llength(lp) == 0) {
1.2       vincent   278:                skip = 1;
1.3       vincent   279:        }
1.1       vincent   280:
                    281:        /*
                    282:         * Again, try to reuse last undo record, if we can
                    283:         */
1.7     ! vincent   284:        rec = LIST_FIRST(&curbp->b_undo);
1.2       vincent   285:        if (!skip &&
                    286:            (rec != NULL) &&
1.1       vincent   287:            (rec->type == DELETE) &&
                    288:            (rec->region.r_linep == reg.r_linep)) {
                    289:                char *newbuf;
                    290:                int newlen;
                    291:
                    292:                dist = rec->region.r_offset - reg.r_offset;
                    293:                if (rec->region.r_size >= dist) {
                    294:                        newlen = rec->region.r_size + reg.r_size;
                    295:
                    296:                        do {
                    297:                                newbuf = malloc(newlen * sizeof(char));
                    298:                        } while (newbuf == NULL && drop_oldest_undo_record());
                    299:
                    300:                        if (newbuf == NULL)
                    301:                                panic("out of memory in undo delete code");
                    302:
                    303:                        /*
                    304:                         * [new data][old data]
                    305:                         */
                    306:                        region_get_data(&reg, newbuf, size);
                    307:                        memmove(newbuf + reg.r_size, rec->content,
                    308:                            rec->region.r_size);
                    309:
                    310:                        rec->pos = pos;
                    311:                        rec->region.r_offset = reg.r_offset;
                    312:                        rec->region.r_size = newlen;
                    313:                        if (rec->content != NULL)
                    314:                                free(rec->content);
                    315:                        rec->content = newbuf;
                    316:
                    317:                        return TRUE;
                    318:                }
                    319:        }
                    320:
                    321:        /*
                    322:         * So we couldn't reuse the last undo record? Just allocate a new
                    323:         * one.
                    324:         */
                    325:        rec = new_undo_record();
                    326:        rec->pos = pos;
                    327:
                    328:        rec->type = DELETE;
                    329:        memmove(&rec->region, &reg, sizeof(REGION));
                    330:        do {
                    331:                rec->content = malloc(reg.r_size + 1);
                    332:        } while ((rec->content == NULL) && drop_oldest_undo_record());
                    333:
                    334:        if (rec->content == NULL)
                    335:                panic("Out of memory");
                    336:
                    337:        region_get_data(&reg, rec->content, reg.r_size);
                    338:
1.7     ! vincent   339:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1       vincent   340:
                    341:        return TRUE;
                    342: }
                    343:
                    344: /*
                    345:  * This of course must be called before the change takes place
                    346:  */
                    347: int
                    348: undo_add_change(LINE *lp, int offset, int size)
                    349: {
                    350:        REGION reg;
                    351:        struct undo_rec *rec;
                    352:
                    353:
                    354:        if (undo_disable_flag)
                    355:                return TRUE;
                    356:
                    357:        reg.r_linep = lp;
                    358:        reg.r_offset = offset;
                    359:        reg.r_size = size;
                    360:
                    361:        rec = new_undo_record();
                    362:        rec->pos = find_offset(lp, offset);
                    363:        rec->type = CHANGE;
                    364:        memmove(&rec->region, &reg, sizeof reg);
                    365:
                    366:        do {
                    367:                rec->content = malloc(size + 1);
                    368:        } while ((rec->content == NULL) && drop_oldest_undo_record());
                    369:
                    370:        if (rec->content == NULL)
                    371:                panic("Out of memory in undo change code");
                    372:
                    373:        region_get_data(&reg, rec->content, size);
                    374:
1.7     ! vincent   375:        LIST_INSERT_HEAD(&curbp->b_undo, rec, next);
1.1       vincent   376:
                    377:        return TRUE;
                    378: }
                    379:
                    380: int
1.4       vincent   381: undo(int f, int n)
1.1       vincent   382: {
                    383:        struct undo_rec *rec;
                    384:        LINE *ln;
                    385:        int off;
                    386:
                    387:        /*
1.4       vincent   388:         * Let called functions know they are below us (for
                    389:         * example, ldelete don't want to record an undo record
                    390:         * when called by us)
1.1       vincent   391:         */
                    392:        undoaction++;
                    393:
1.6       vincent   394:        while (n > 0) {
1.7     ! vincent   395:                rec = LIST_FIRST(&curbp->b_undo);
1.4       vincent   396:                if (rec == NULL) {
                    397:                        ewprintf("Nothing to undo!");
                    398:                        return FALSE;
                    399:                }
                    400:                LIST_REMOVE(rec, next);
                    401:                if (rec->type == BOUNDARY) {
                    402:                        continue;
                    403:                }
1.1       vincent   404:
1.4       vincent   405:                find_linep(rec->pos, &ln, &off);
                    406:                if (ln == NULL)
                    407:                        return FALSE;
                    408:
                    409:                /*
                    410:                 * Move to where this record has to apply
                    411:                 */
                    412:                curwp->w_dotp = ln;
                    413:                curwp->w_doto = off;
                    414:
                    415:                switch (rec->type) {
                    416:                case INSERT:
                    417:                        ldelete(rec->region.r_size, KFORW);
                    418:                        break;
                    419:                case DELETE:
                    420:                        region_put_data(rec->content, rec->region.r_size);
                    421:                        break;
                    422:                case CHANGE:
                    423:                        forwchar(0, rec->region.r_size);
                    424:                        lreplace(rec->region.r_size, rec->content, 1);
                    425:                        break;
                    426:                default:
                    427:                        break;
                    428:                }
                    429:
                    430:                free_undo_record(rec);
1.6       vincent   431:
                    432:                n--;
1.1       vincent   433:        }
                    434:        undoaction--;
                    435:
                    436:        return TRUE;
                    437: }