plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

elog.c (7458B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <draw.h>
      4 #include <thread.h>
      5 #include <cursor.h>
      6 #include <mouse.h>
      7 #include <keyboard.h>
      8 #include <frame.h>
      9 #include <fcall.h>
     10 #include <plumb.h>
     11 #include <libsec.h>
     12 #include "dat.h"
     13 #include "fns.h"
     14 #include "edit.h"
     15 
     16 static char Wsequence[] = "warning: changes out of sequence\n";
     17 static int	warned = FALSE;
     18 
     19 /*
     20  * Log of changes made by editing commands.  Three reasons for this:
     21  * 1) We want addresses in commands to apply to old file, not file-in-change.
     22  * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
     23  * 3) This gives an opportunity to optimize by merging adjacent changes.
     24  * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
     25  * separate implementation.  To do this well, we use Replace as well as
     26  * Insert and Delete
     27  */
     28 
     29 typedef struct Buflog Buflog;
     30 struct Buflog
     31 {
     32 	short	type;		/* Replace, Filename */
     33 	uint		q0;		/* location of change (unused in f) */
     34 	uint		nd;		/* # runes to delete */
     35 	uint		nr;		/* # runes in string or file name */
     36 };
     37 
     38 enum
     39 {
     40 	Buflogsize = sizeof(Buflog)/sizeof(Rune)
     41 };
     42 
     43 /*
     44  * Minstring shouldn't be very big or we will do lots of I/O for small changes.
     45  * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
     46  */
     47 enum
     48 {
     49 	Minstring = 16,		/* distance beneath which we merge changes */
     50 	Maxstring = RBUFSIZE	/* maximum length of change we will merge into one */
     51 };
     52 
     53 void
     54 eloginit(File *f)
     55 {
     56 	if(f->elog.type != Empty)
     57 		return;
     58 	f->elog.type = Null;
     59 	if(f->elogbuf == nil)
     60 		f->elogbuf = emalloc(sizeof(Buffer));
     61 	if(f->elog.r == nil)
     62 		f->elog.r = fbufalloc();
     63 	bufreset(f->elogbuf);
     64 }
     65 
     66 void
     67 elogclose(File *f)
     68 {
     69 	if(f->elogbuf){
     70 		bufclose(f->elogbuf);
     71 		free(f->elogbuf);
     72 		f->elogbuf = nil;
     73 	}
     74 }
     75 
     76 void
     77 elogreset(File *f)
     78 {
     79 	f->elog.type = Null;
     80 	f->elog.nd = 0;
     81 	f->elog.nr = 0;
     82 }
     83 
     84 void
     85 elogterm(File *f)
     86 {
     87 	elogreset(f);
     88 	if(f->elogbuf)
     89 		bufreset(f->elogbuf);
     90 	f->elog.type = Empty;
     91 	fbuffree(f->elog.r);
     92 	f->elog.r = nil;
     93 	warned = FALSE;
     94 }
     95 
     96 void
     97 elogflush(File *f)
     98 {
     99 	Buflog b;
    100 
    101 	b.type = f->elog.type;
    102 	b.q0 = f->elog.q0;
    103 	b.nd = f->elog.nd;
    104 	b.nr = f->elog.nr;
    105 	switch(f->elog.type){
    106 	default:
    107 		warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
    108 		break;
    109 	case Null:
    110 		break;
    111 	case Insert:
    112 	case Replace:
    113 		if(f->elog.nr > 0)
    114 			bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
    115 		/* fall through */
    116 	case Delete:
    117 		bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
    118 		break;
    119 	}
    120 	elogreset(f);
    121 }
    122 
    123 void
    124 elogreplace(File *f, int q0, int q1, Rune *r, int nr)
    125 {
    126 	uint gap;
    127 
    128 	if(q0==q1 && nr==0)
    129 		return;
    130 	eloginit(f);
    131 	if(f->elog.type!=Null && q0<f->elog.q0){
    132 		if(warned++ == 0)
    133 			warning(nil, Wsequence);
    134 		elogflush(f);
    135 	}
    136 	/* try to merge with previous */
    137 	gap = q0 - (f->elog.q0+f->elog.nd);	/* gap between previous and this */
    138 	if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
    139 		if(gap < Minstring){
    140 			if(gap > 0){
    141 				bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
    142 				f->elog.nr += gap;
    143 			}
    144 			f->elog.nd += gap + q1-q0;
    145 			runemove(f->elog.r+f->elog.nr, r, nr);
    146 			f->elog.nr += nr;
    147 			return;
    148 		}
    149 	}
    150 	elogflush(f);
    151 	f->elog.type = Replace;
    152 	f->elog.q0 = q0;
    153 	f->elog.nd = q1-q0;
    154 	f->elog.nr = nr;
    155 	if(nr > RBUFSIZE)
    156 		editerror("internal error: replacement string too large(%d)", nr);
    157 	runemove(f->elog.r, r, nr);
    158 }
    159 
    160 void
    161 eloginsert(File *f, int q0, Rune *r, int nr)
    162 {
    163 	int n;
    164 
    165 	if(nr == 0)
    166 		return;
    167 	eloginit(f);
    168 	if(f->elog.type!=Null && q0<f->elog.q0){
    169 		if(warned++ == 0)
    170 			warning(nil, Wsequence);
    171 		elogflush(f);
    172 	}
    173 	/* try to merge with previous */
    174 	if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
    175 		runemove(f->elog.r+f->elog.nr, r, nr);
    176 		f->elog.nr += nr;
    177 		return;
    178 	}
    179 	while(nr > 0){
    180 		elogflush(f);
    181 		f->elog.type = Insert;
    182 		f->elog.q0 = q0;
    183 		n = nr;
    184 		if(n > RBUFSIZE)
    185 			n = RBUFSIZE;
    186 		f->elog.nr = n;
    187 		runemove(f->elog.r, r, n);
    188 		r += n;
    189 		nr -= n;
    190 	}
    191 }
    192 
    193 void
    194 elogdelete(File *f, int q0, int q1)
    195 {
    196 	if(q0 == q1)
    197 		return;
    198 	eloginit(f);
    199 	if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
    200 		if(warned++ == 0)
    201 			warning(nil, Wsequence);
    202 		elogflush(f);
    203 	}
    204 	/* try to merge with previous */
    205 	if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
    206 		f->elog.nd += q1-q0;
    207 		return;
    208 	}
    209 	elogflush(f);
    210 	f->elog.type = Delete;
    211 	f->elog.q0 = q0;
    212 	f->elog.nd = q1-q0;
    213 }
    214 
    215 #define tracelog 0
    216 void
    217 elogapply(File *f)
    218 {
    219 	Buflog b;
    220 	Rune *buf;
    221 	uint i, n, up, mod;
    222 	uint tq0, tq1;
    223 	Buffer *log;
    224 	Text *t;
    225 	int owner;
    226 
    227 	elogflush(f);
    228 	log = f->elogbuf;
    229 	t = f->curtext;
    230 
    231 	buf = fbufalloc();
    232 	mod = FALSE;
    233 
    234 	owner = 0;
    235 	if(t->w){
    236 		owner = t->w->owner;
    237 		if(owner == 0)
    238 			t->w->owner = 'E';
    239 	}
    240 
    241 	/*
    242 	 * The edit commands have already updated the selection in t->q0, t->q1,
    243 	 * but using coordinates relative to the unmodified buffer.  As we apply the log,
    244 	 * we have to update the coordinates to be relative to the modified buffer.
    245 	 * Textinsert and textdelete will do this for us; our only work is to apply the
    246 	 * convention that an insertion at t->q0==t->q1 is intended to select the
    247 	 * inserted text.
    248 	 */
    249 
    250 	/*
    251 	 * We constrain the addresses in here (with textconstrain()) because
    252 	 * overlapping changes will generate bogus addresses.   We will warn
    253 	 * about changes out of sequence but proceed anyway; here we must
    254 	 * keep things in range.
    255 	 */
    256 
    257 	while(log->nc > 0){
    258 		up = log->nc-Buflogsize;
    259 		bufread(log, up, (Rune*)&b, Buflogsize);
    260 		switch(b.type){
    261 		default:
    262 			fprint(2, "elogapply: 0x%ux\n", b.type);
    263 			abort();
    264 			break;
    265 
    266 		case Replace:
    267 			if(tracelog)
    268 				warning(nil, "elog replace %d %d (%d %d)\n",
    269 					b.q0, b.q0+b.nd, t->q0, t->q1);
    270 			if(!mod){
    271 				mod = TRUE;
    272 				filemark(f);
    273 			}
    274 			textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
    275 			textdelete(t, tq0, tq1, TRUE);
    276 			up -= b.nr;
    277 			for(i=0; i<b.nr; i+=n){
    278 				n = b.nr - i;
    279 				if(n > RBUFSIZE)
    280 					n = RBUFSIZE;
    281 				bufread(log, up+i, buf, n);
    282 				textinsert(t, tq0+i, buf, n, TRUE);
    283 			}
    284 			if(t->q0 == b.q0 && t->q1 == b.q0)
    285 				t->q1 += b.nr;
    286 			break;
    287 
    288 		case Delete:
    289 			if(tracelog)
    290 				warning(nil, "elog delete %d %d (%d %d)\n",
    291 					b.q0, b.q0+b.nd, t->q0, t->q1);
    292 			if(!mod){
    293 				mod = TRUE;
    294 				filemark(f);
    295 			}
    296 			textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
    297 			textdelete(t, tq0, tq1, TRUE);
    298 			break;
    299 
    300 		case Insert:
    301 			if(tracelog)
    302 				warning(nil, "elog insert %d %d (%d %d)\n",
    303 					b.q0, b.q0+b.nr, t->q0, t->q1);
    304 			if(!mod){
    305 				mod = TRUE;
    306 				filemark(f);
    307 			}
    308 			textconstrain(t, b.q0, b.q0, &tq0, &tq1);
    309 			up -= b.nr;
    310 			for(i=0; i<b.nr; i+=n){
    311 				n = b.nr - i;
    312 				if(n > RBUFSIZE)
    313 					n = RBUFSIZE;
    314 				bufread(log, up+i, buf, n);
    315 				textinsert(t, tq0+i, buf, n, TRUE);
    316 			}
    317 			if(t->q0 == b.q0 && t->q1 == b.q0)
    318 				t->q1 += b.nr;
    319 			break;
    320 
    321 /*		case Filename:
    322 			f->seq = u.seq;
    323 			fileunsetname(f, epsilon);
    324 			f->mod = u.mod;
    325 			up -= u.n;
    326 			free(f->name);
    327 			if(u.n == 0)
    328 				f->name = nil;
    329 			else
    330 				f->name = runemalloc(u.n);
    331 			bufread(delta, up, f->name, u.n);
    332 			f->nname = u.n;
    333 			break;
    334 */
    335 		}
    336 		bufdelete(log, up, log->nc);
    337 	}
    338 	fbuffree(buf);
    339 	elogterm(f);
    340 
    341 	/*
    342 	 * Bad addresses will cause bufload to crash, so double check.
    343 	 * If changes were out of order, we expect problems so don't complain further.
    344 	 */
    345 	if(t->q0 > f->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){
    346 		if(!warned)
    347 			warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc);
    348 		t->q1 = min(t->q1, f->b.nc);
    349 		t->q0 = min(t->q0, t->q1);
    350 	}
    351 
    352 	if(t->w)
    353 		t->w->owner = owner;
    354 }