Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
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
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
76
elogreset(File *f)
77
{
78
	f->elog.type = Null;
79
	f->elog.nd = 0;
80
	f->elog.nr = 0;
81
}
82
 
83
void
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
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
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
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
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
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
}