Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include "dat.h"
#include "fns.h"
#include "edit.h"

static char Wsequence[] = "warning: changes out of sequence\n";
static int      warned = FALSE;

/*
 * Log of changes made by editing commands.  Three reasons for this:
 * 1) We want addresses in commands to apply to old file, not file-in-change.
 * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
 * 3) This gives an opportunity to optimize by merging adjacent changes.
 * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
 * separate implementation.  To do this well, we use Replace as well as
 * Insert and Delete
 */

typedef struct Buflog Buflog;
struct Buflog
{
        short   type;           /* Replace, Filename */
        uint            q0;             /* location of change (unused in f) */
        uint            nd;             /* # runes to delete */
        uint            nr;             /* # runes in string or file name */
};

enum
{
        Buflogsize = sizeof(Buflog)/sizeof(Rune),
};

/*
 * Minstring shouldn't be very big or we will do lots of I/O for small changes.
 * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
 */
enum
{
        Minstring = 16,         /* distance beneath which we merge changes */
        Maxstring = RBUFSIZE,   /* maximum length of change we will merge into one */
};

void
eloginit(File *f)
{
        if(f->elog.type != Empty)
                return;
        f->elog.type = Null;
        if(f->elogbuf == nil)
                f->elogbuf = emalloc(sizeof(Buffer));
        if(f->elog.r == nil)
                f->elog.r = fbufalloc();
        bufreset(f->elogbuf);
}

void
elogclose(File *f)
{
        if(f->elogbuf){
                bufclose(f->elogbuf);
                free(f->elogbuf);
                f->elogbuf = nil;
        }
}

void
elogreset(File *f)
{
        f->elog.type = Null;
        f->elog.nd = 0;
        f->elog.nr = 0;
}

void
elogterm(File *f)
{
        elogreset(f);
        if(f->elogbuf)
                bufreset(f->elogbuf);
        f->elog.type = Empty;
        fbuffree(f->elog.r);
        f->elog.r = nil;
        warned = FALSE;
}

void
elogflush(File *f)
{
        Buflog b;

        b.type = f->elog.type;
        b.q0 = f->elog.q0;
        b.nd = f->elog.nd;
        b.nr = f->elog.nr;
        switch(f->elog.type){
        default:
                warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
                break;
        case Null:
                break;
        case Insert:
        case Replace:
                if(f->elog.nr > 0)
                        bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
                /* fall through */
        case Delete:
                bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
                break;
        }
        elogreset(f);
}

void
elogreplace(File *f, int q0, int q1, Rune *r, int nr)
{
        uint gap;

        if(q0==q1 && nr==0)
                return;
        eloginit(f);
        if(f->elog.type!=Null && q0<f->elog.q0){
                if(warned++ == 0)
                        warning(nil, Wsequence);
                elogflush(f);
        }
        /* try to merge with previous */
        gap = q0 - (f->elog.q0+f->elog.nd);     /* gap between previous and this */
        if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
                if(gap < Minstring){
                        if(gap > 0){
                                bufread(f, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
                                f->elog.nr += gap;
                        }
                        f->elog.nd += gap + q1-q0;
                        runemove(f->elog.r+f->elog.nr, r, nr);
                        f->elog.nr += nr;
                        return;
                }
        }
        elogflush(f);
        f->elog.type = Replace;
        f->elog.q0 = q0;
        f->elog.nd = q1-q0;
        f->elog.nr = nr;
        if(nr > RBUFSIZE)
                editerror("internal error: replacement string too large(%d)", nr);
        runemove(f->elog.r, r, nr);
}

void
eloginsert(File *f, int q0, Rune *r, int nr)
{
        int n;

        if(nr == 0)
                return;
        eloginit(f);
        if(f->elog.type!=Null && q0<f->elog.q0){
                if(warned++ == 0)
                        warning(nil, Wsequence);
                elogflush(f);
        }
        /* try to merge with previous */
        if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
                runemove(f->elog.r+f->elog.nr, r, nr);
                f->elog.nr += nr;
                return;
        }
        while(nr > 0){
                elogflush(f);
                f->elog.type = Insert;
                f->elog.q0 = q0;
                n = nr;
                if(n > RBUFSIZE)
                        n = RBUFSIZE;
                f->elog.nr = n;
                runemove(f->elog.r, r, n);
                r += n;
                nr -= n;
        }
}

void
elogdelete(File *f, int q0, int q1)
{
        if(q0 == q1)
                return;
        eloginit(f);
        if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
                if(warned++ == 0)
                        warning(nil, Wsequence);
                elogflush(f);
        }
        /* try to merge with previous */
        if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
                f->elog.nd += q1-q0;
                return;
        }
        elogflush(f);
        f->elog.type = Delete;
        f->elog.q0 = q0;
        f->elog.nd = q1-q0;
}

#define tracelog 0
void
elogapply(File *f)
{
        Buflog b;
        Rune *buf;
        uint i, n, up, mod;
        uint tq0, tq1;
        Buffer *log;
        Text *t;
        int owner;

        elogflush(f);
        log = f->elogbuf;
        t = f->curtext;

        buf = fbufalloc();
        mod = FALSE;

        owner = 0;
        if(t->w){
                owner = t->w->owner;
                if(owner == 0)
                        t->w->owner = 'E';
        }

        /*
         * The edit commands have already updated the selection in t->q0, t->q1,
         * but using coordinates relative to the unmodified buffer.  As we apply the log,
         * we have to update the coordinates to be relative to the modified buffer.
         * Textinsert and textdelete will do this for us; our only work is to apply the
         * convention that an insertion at t->q0==t->q1 is intended to select the 
         * inserted text.
         */

        /*
         * We constrain the addresses in here (with textconstrain()) because
         * overlapping changes will generate bogus addresses.   We will warn
         * about changes out of sequence but proceed anyway; here we must
         * keep things in range.
         */

        while(log->nc > 0){
                up = log->nc-Buflogsize;
                bufread(log, up, (Rune*)&b, Buflogsize);
                switch(b.type){
                default:
                        fprint(2, "elogapply: 0x%ux\n", b.type);
                        abort();
                        break;

                case Replace:
                        if(tracelog)
                                warning(nil, "elog replace %d %d (%d %d)\n",
                                        b.q0, b.q0+b.nd, t->q0, t->q1);
                        if(!mod){
                                mod = TRUE;
                                filemark(f);
                        }
                        textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
                        textdelete(t, tq0, tq1, TRUE);
                        up -= b.nr;
                        for(i=0; i<b.nr; i+=n){
                                n = b.nr - i;
                                if(n > RBUFSIZE)
                                        n = RBUFSIZE;
                                bufread(log, up+i, buf, n);
                                textinsert(t, tq0+i, buf, n, TRUE);
                        }
                        if(t->q0 == b.q0 && t->q1 == b.q0)
                                t->q1 += b.nr;
                        break;

                case Delete:
                        if(tracelog)
                                warning(nil, "elog delete %d %d (%d %d)\n",
                                        b.q0, b.q0+b.nd, t->q0, t->q1);
                        if(!mod){
                                mod = TRUE;
                                filemark(f);
                        }
                        textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
                        textdelete(t, tq0, tq1, TRUE);
                        break;

                case Insert:
                        if(tracelog)
                                warning(nil, "elog insert %d %d (%d %d)\n",
                                        b.q0, b.q0+b.nr, t->q0, t->q1);
                        if(!mod){
                                mod = TRUE;
                                filemark(f);
                        }
                        textconstrain(t, b.q0, b.q0, &tq0, &tq1);
                        up -= b.nr;
                        for(i=0; i<b.nr; i+=n){
                                n = b.nr - i;
                                if(n > RBUFSIZE)
                                        n = RBUFSIZE;
                                bufread(log, up+i, buf, n);
                                textinsert(t, tq0+i, buf, n, TRUE);
                        }
                        if(t->q0 == b.q0 && t->q1 == b.q0)
                                t->q1 += b.nr;
                        break;

/*              case Filename:
                        f->seq = u.seq;
                        fileunsetname(f, epsilon);
                        f->mod = u.mod;
                        up -= u.n;
                        free(f->name);
                        if(u.n == 0)
                                f->name = nil;
                        else
                                f->name = runemalloc(u.n);
                        bufread(delta, up, f->name, u.n);
                        f->nname = u.n;
                        break;
*/
                }
                bufdelete(log, up, log->nc);
        }
        fbuffree(buf);
        elogterm(f);

        /*
         * Bad addresses will cause bufload to crash, so double check.
         * If changes were out of order, we expect problems so don't complain further.
         */
        if(t->q0 > f->nc || t->q1 > f->nc || t->q0 > t->q1){
                if(!warned)
                        warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->nc);
                t->q1 = min(t->q1, f->nc);
                t->q0 = min(t->q0, t->q1);
        }

        if(t->w)
                t->w->owner = owner;
}