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
eloginit(File * f)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
elogclose(File * f)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
elogreset(File * f)77 elogreset(File *f)
78 {
79 	f->elog.type = Null;
80 	f->elog.nd = 0;
81 	f->elog.nr = 0;
82 }
83 
84 void
elogterm(File * f)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
elogflush(File * f)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
elogreplace(File * f,int q0,int q1,Rune * r,int nr)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
eloginsert(File * f,int q0,Rune * r,int nr)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
elogdelete(File * f,int q0,int q1)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
elogapply(File * f)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 }
355