123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- /*
- * This file is part of the UCB release of Plan 9. It is subject to the license
- * terms in the LICENSE file found in the top-level directory of this
- * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
- * part of the UCB release of Plan 9, including this file, may be copied,
- * modified, propagated, or distributed except according to the terms contained
- * in the LICENSE file.
- */
- #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
- {
- int16_t 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%x\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->Buffer, 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%x\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->Buffer.nc || t->q1 > f->Buffer.nc || t->q0 > t->q1){
- if(!warned)
- warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->Buffer.nc);
- t->q1 = min(t->q1, f->Buffer.nc);
- t->q0 = min(t->q0, t->q1);
- }
- if(t->w)
- t->w->owner = owner;
- }
|