xref: /inferno-os/appl/acme/elog.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Editlog;
2
3include "common.m";
4
5sys: Sys;
6utils: Utils;
7buffm: Bufferm;
8filem: Filem;
9textm: Textm;
10edit: Edit;
11
12sprint, fprint: import sys;
13FALSE, TRUE, BUFSIZE, Empty, Null, Delete, Insert, Replace, Filename, Astring: import Dat;
14File: import filem;
15Buffer: import buffm;
16Text: import textm;
17error, warning, stralloc, strfree: import utils;
18editerror: import edit;
19
20init(mods : ref Dat->Mods)
21{
22	sys = mods.sys;
23	utils = mods.utils;
24	buffm = mods.bufferm;
25	filem = mods.filem;
26	textm = mods.textm;
27	edit = mods.edit;
28}
29
30Wsequence := "warning: changes out of sequence\n";
31warned := FALSE;
32
33#
34# Log of changes made by editing commands.  Three reasons for this:
35# 1) We want addresses in commands to apply to old file, not file-in-change.
36# 2) It's difficult to track changes correctly as things move, e.g. ,x m$
37# 3) This gives an opportunity to optimize by merging adjacent changes.
38# It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
39# separate implementation.  To do this well, we use Replace as well as
40# Insert and Delete
41#
42
43Buflog: adt{
44	typex: int;		# Replace, Filename
45	q0: int;		# location of change (unused in f)
46	nd: int;		# runes to delete
47	nr: int;		# runes in string or file name
48};
49
50Buflogsize: con 7;
51SHM : con 16rffff;
52
53pack(b: Buflog) : string
54{
55	a := "0123456";
56	a[0] = b.typex;
57	a[1] = b.q0&SHM;
58	a[2] = (b.q0>>16)&SHM;
59	a[3] = b.nd&SHM;
60	a[4] = (b.nd>>16)&SHM;
61	a[5] = b.nr&SHM;
62	a[6] = (b.nr>>16)&SHM;
63	return a;
64}
65
66scopy(s1: ref Astring, m: int, s2: string, n: int, o: int)
67{
68	p := o-n;
69	for(i := 0; i < p; i++)
70		s1.s[m++] = s2[n++];
71}
72
73#
74# Minstring shouldn't be very big or we will do lots of I/O for small changes.
75# Maxstring is BUFSIZE so we can fbufalloc() once and not realloc elog.r.
76#
77Minstring: con 16;	# distance beneath which we merge changes
78Maxstring: con BUFSIZE;	# maximum length of change we will merge into one
79
80eloginit(f: ref File)
81{
82	if(f.elog.typex != Empty)
83		return;
84	f.elog.typex = Null;
85	if(f.elogbuf == nil)
86		f.elogbuf = buffm->newbuffer();
87		# f.elogbuf = ref Buffer;
88	if(f.elog.r == nil)
89		f.elog.r = stralloc(BUFSIZE);
90	f.elogbuf.reset();
91}
92
93elogclose(f: ref File)
94{
95	if(f.elogbuf != nil){
96		f.elogbuf.close();
97		f.elogbuf = nil;
98	}
99}
100
101elogreset(f: ref File)
102{
103	f.elog.typex = Null;
104	f.elog.nd = 0;
105	f.elog.nr = 0;
106}
107
108elogterm(f: ref File)
109{
110	elogreset(f);
111	if(f.elogbuf != nil)
112		f.elogbuf.reset();
113	f.elog.typex = Empty;
114	if(f.elog.r != nil){
115		strfree(f.elog.r);
116		f.elog.r = nil;
117	}
118	warned = FALSE;
119}
120
121elogflush(f: ref File)
122{
123	b: Buflog;
124
125	b.typex = f.elog.typex;
126	b.q0 = f.elog.q0;
127	b.nd = f.elog.nd;
128	b.nr = f.elog.nr;
129	case(f.elog.typex){
130	* =>
131		warning(nil, sprint("unknown elog type 0x%ux\n", f.elog.typex));
132		break;
133	Null =>
134		break;
135	Insert or
136	Replace =>
137		if(f.elog.nr > 0)
138			f.elogbuf.insert(f.elogbuf.nc, f.elog.r.s, f.elog.nr);
139		f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize);
140		break;
141	Delete =>
142		f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize);
143		break;
144	}
145	elogreset(f);
146}
147
148elogreplace(f: ref File, q0: int, q1: int, r: string, nr: int)
149{
150	gap: int;
151
152	if(q0==q1 && nr==0)
153		return;
154	eloginit(f);
155	if(f.elog.typex!=Null && q0<f.elog.q0){
156		if(warned++ == 0)
157			warning(nil, Wsequence);
158		elogflush(f);
159	}
160	# try to merge with previous
161	gap = q0 - (f.elog.q0+f.elog.nd);	# gap between previous and this
162	if(f.elog.typex==Replace && f.elog.nr+gap+nr<Maxstring){
163		if(gap < Minstring){
164			if(gap > 0){
165				f.buf.read(f.elog.q0+f.elog.nd, f.elog.r, f.elog.nr, gap);
166				f.elog.nr += gap;
167			}
168			f.elog.nd += gap + q1-q0;
169			scopy(f.elog.r, f.elog.nr, r, 0, nr);
170			f.elog.nr += nr;
171			return;
172		}
173	}
174	elogflush(f);
175	f.elog.typex = Replace;
176	f.elog.q0 = q0;
177	f.elog.nd = q1-q0;
178	f.elog.nr = nr;
179	if(nr > BUFSIZE)
180		editerror(sprint("internal error: replacement string too large(%d)", nr));
181	scopy(f.elog.r, 0, r, 0, nr);
182}
183
184eloginsert(f: ref File, q0: int, r: string, nr: int)
185{
186	n: int;
187
188	if(nr == 0)
189		return;
190	eloginit(f);
191	if(f.elog.typex!=Null && q0<f.elog.q0){
192		if(warned++ == 0)
193			warning(nil, Wsequence);
194		elogflush(f);
195	}
196	# try to merge with previous
197	if(f.elog.typex==Insert && q0==f.elog.q0 && f.elog.nr+nr<Maxstring){
198		ofer := f.elog.r;
199		f.elog.r = stralloc(f.elog.nr+nr);
200		scopy(f.elog.r, 0, ofer.s, 0, f.elog.nr);
201		scopy(f.elog.r, f.elog.nr, r, 0, nr);
202		f.elog.nr += nr;
203		strfree(ofer);
204		return;
205	}
206	while(nr > 0){
207		elogflush(f);
208		f.elog.typex = Insert;
209		f.elog.q0 = q0;
210		n = nr;
211		if(n > BUFSIZE)
212			n = BUFSIZE;
213		f.elog.nr = n;
214		scopy(f.elog.r, 0, r, 0, n);
215		r = r[n:];
216		nr -= n;
217	}
218}
219
220elogdelete(f: ref File, q0: int, q1: int)
221{
222	if(q0 == q1)
223		return;
224	eloginit(f);
225	if(f.elog.typex!=Null && q0<f.elog.q0+f.elog.nd){
226		if(warned++ == 0)
227			warning(nil, Wsequence);
228		elogflush(f);
229	}
230	#  try to merge with previous
231	if(f.elog.typex==Delete && f.elog.q0+f.elog.nd==q0){
232		f.elog.nd += q1-q0;
233		return;
234	}
235	elogflush(f);
236	f.elog.typex = Delete;
237	f.elog.q0 = q0;
238	f.elog.nd = q1-q0;
239}
240
241elogapply(f: ref File)
242{
243	b: Buflog;
244	buf: ref Astring;
245	i, n, up, mod : int;
246	log: ref Buffer;
247
248	elogflush(f);
249	log = f.elogbuf;
250	t := f.curtext;
251
252	a := stralloc(Buflogsize);
253	buf = stralloc(BUFSIZE);
254	mod = FALSE;
255
256	#
257	# The edit commands have already updated the selection in t.q0, t.q1.
258	# The text.insert and text.delete calls below will update it again, so save the
259	# current setting and restore it at the end.
260	#
261	q0 := t.q0;
262	q1 := t.q1;
263
264	while(log.nc > 0){
265		up = log.nc-Buflogsize;
266		log.read(up, a, 0, Buflogsize);
267		b.typex = a.s[0];
268		b.q0 = a.s[1]|(a.s[2]<<16);
269		b.nd = a.s[3]|(a.s[4]<<16);
270		b.nr = a.s[5]|(a.s[6]<<16);
271		case(b.typex){
272		* =>
273			error(sprint("elogapply: 0x%ux\n", b.typex));
274			break;
275
276		Replace =>
277			if(!mod){
278				mod = TRUE;
279				f.mark();
280			}
281			# if(b.nd == b.nr && b.nr <= BUFSIZE){
282			#	up -= b.nr;
283			#	log.read(up, buf, 0, b.nr);
284			#	t.replace(b.q0, b.q0+b.nd, buf.s, b.nr, TRUE, 0);
285			#	break;
286			# }
287			t.delete(b.q0, b.q0+b.nd, TRUE);
288			up -= b.nr;
289			for(i=0; i<b.nr; i+=n){
290				n = b.nr - i;
291				if(n > BUFSIZE)
292					n = BUFSIZE;
293				log.read(up+i, buf, 0, n);
294				t.insert(b.q0+i, buf.s, n, TRUE, 0);
295			}
296			# t.q0 = b.q0;
297			# t.q1 = b.q0+b.nr;
298			break;
299
300		Delete =>
301			if(!mod){
302				mod = TRUE;
303				f.mark();
304			}
305			t.delete(b.q0, b.q0+b.nd, TRUE);
306			# t.q0 = b.q0;
307			# t.q1 = b.q0;
308			break;
309
310		Insert =>
311			if(!mod){
312				mod = TRUE;
313				f.mark();
314			}
315			up -= b.nr;
316			for(i=0; i<b.nr; i+=n){
317				n = b.nr - i;
318				if(n > BUFSIZE)
319					n = BUFSIZE;
320				log.read(up+i, buf, 0, n);
321				t.insert(b.q0+i, buf.s, n, TRUE, 0);
322			}
323			# t.q0 = b.q0;
324			# t.q1 = b.q0+b.nr;
325			break;
326
327#		Filename =>
328#			f.seq = u.seq;
329#			f.unsetname(epsilon);
330#			f.mod = u.mod;
331#			up -= u.n;
332#			if(u.n == 0)
333#				f.name = nil;
334#			else{
335#				fn0 := stralloc(u.n);
336#				delta.read(up, fn0, 0, u.n);
337#				f.name = fn0.s;
338#				strfree(fn0);
339#			}
340#			break;
341#
342		}
343		log.delete(up, log.nc);
344	}
345	strfree(buf);
346	strfree(a);
347	elogterm(f);
348
349	t.q0 = q0;
350	t.q1 = q1;
351	if(t.q1 > f.buf.nc)	# can't happen
352		t.q1 = f.buf.nc;
353}
354