Blob


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"
15 static char Wsequence[] = "warning: changes out of sequence\n";
16 static int warned = FALSE;
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 */
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 };
37 enum
38 {
39 Buflogsize = sizeof(Buflog)/sizeof(Rune)
40 };
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 };
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 }
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 }
75 void
76 elogreset(File *f)
77 {
78 f->elog.type = Null;
79 f->elog.nd = 0;
80 f->elog.nr = 0;
81 }
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 }
95 void
96 elogflush(File *f)
97 {
98 Buflog b;
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;
119 elogreset(f);
122 void
123 elogreplace(File *f, int q0, int q1, Rune *r, int nr)
125 uint gap;
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);
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->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
141 f->elog.nr += gap;
143 f->elog.nd += gap + q1-q0;
144 runemove(f->elog.r+f->elog.nr, r, nr);
145 f->elog.nr += nr;
146 return;
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);
159 void
160 eloginsert(File *f, int q0, Rune *r, int nr)
162 int n;
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);
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;
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;
192 void
193 elogdelete(File *f, int q0, int q1)
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);
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;
208 elogflush(f);
209 f->elog.type = Delete;
210 f->elog.q0 = q0;
211 f->elog.nd = q1-q0;
214 #define tracelog 0
215 void
216 elogapply(File *f)
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;
226 elogflush(f);
227 log = f->elogbuf;
228 t = f->curtext;
230 buf = fbufalloc();
231 mod = FALSE;
233 owner = 0;
234 if(t->w){
235 owner = t->w->owner;
236 if(owner == 0)
237 t->w->owner = 'E';
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 */
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 */
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;
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);
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);
283 if(t->q0 == b.q0 && t->q1 == b.q0)
284 t->q1 += b.nr;
285 break;
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);
295 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
296 textdelete(t, tq0, tq1, TRUE);
297 break;
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);
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);
316 if(t->q0 == b.q0 && t->q1 == b.q0)
317 t->q1 += b.nr;
318 break;
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 */
335 bufdelete(log, up, log->nc);
337 fbuffree(buf);
338 elogterm(f);
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->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){
345 if(!warned)
346 warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc);
347 t->q1 = min(t->q1, f->b.nc);
348 t->q0 = min(t->q0, t->q1);
351 if(t->w)
352 t->w->owner = owner;