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