diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/acme/elog.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/acme/elog.c')
-rwxr-xr-x | sys/src/cmd/acme/elog.c | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/sys/src/cmd/acme/elog.c b/sys/src/cmd/acme/elog.c new file mode 100755 index 000000000..d00fc8d3b --- /dev/null +++ b/sys/src/cmd/acme/elog.c @@ -0,0 +1,353 @@ +#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; +} |