| #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->b, 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->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){ |
| if(!warned) |
| warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc); |
| t->q1 = min(t->q1, f->b.nc); |
| t->q0 = min(t->q0, t->q1); |
| } |
| |
| if(t->w) |
| t->w->owner = owner; |
| } |