master ohmu/acme2k / src / cmd / acme / elog.c
ohmu  ·  2026-09-21
  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
 16static char Wsequence[] = "warning: changes out of sequence\n";
 17static 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
 29typedef struct Buflog Buflog;
 30struct 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
 38enum
 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 */
 47enum
 48{
 49	Minstring = 16,		/* distance beneath which we merge changes */
 50	Maxstring = RBUFSIZE	/* maximum length of change we will merge into one */
 51};
 52
 53void
 54eloginit(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
 66void
 67elogclose(File *f)
 68{
 69	if(f->elogbuf){
 70		bufclose(f->elogbuf);
 71		free(f->elogbuf);
 72		f->elogbuf = nil;
 73	}
 74}
 75
 76void
 77elogreset(File *f)
 78{
 79	f->elog.type = Null;
 80	f->elog.nd = 0;
 81	f->elog.nr = 0;
 82}
 83
 84void
 85elogterm(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
 96void
 97elogflush(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
123void
124elogreplace(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
160void
161eloginsert(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
193void
194elogdelete(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
216void
217elogapply(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}