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 <libsec.h>
12 #include "dat.h"
13 #include "fns.h"
14 #include "edit.h"
16 static char Wsequence[] = "warning: changes out of sequence\n";
17 static int warned = FALSE;
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 */
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 };
38 enum
39 {
40 Buflogsize = sizeof(Buflog)/sizeof(Rune)
41 };
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 };
53 void
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 }
66 void
67 elogclose(File *f)
68 {
69 if(f->elogbuf){
70 bufclose(f->elogbuf);
71 free(f->elogbuf);
72 f->elogbuf = nil;
73 }
74 }
76 void
77 elogreset(File *f)
78 {
79 f->elog.type = Null;
80 f->elog.nd = 0;
81 f->elog.nr = 0;
82 }
84 void
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 }
96 void
97 elogflush(File *f)
98 {
99 Buflog b;
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;
120 elogreset(f);
123 void
124 elogreplace(File *f, int q0, int q1, Rune *r, int nr)
126 uint gap;
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);
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;
144 f->elog.nd += gap + q1-q0;
145 runemove(f->elog.r+f->elog.nr, r, nr);
146 f->elog.nr += nr;
147 return;
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);
160 void
161 eloginsert(File *f, int q0, Rune *r, int nr)
163 int n;
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);
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;
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;
193 void
194 elogdelete(File *f, int q0, int q1)
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);
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;
209 elogflush(f);
210 f->elog.type = Delete;
211 f->elog.q0 = q0;
212 f->elog.nd = q1-q0;
215 #define tracelog 0
216 void
217 elogapply(File *f)
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;
227 elogflush(f);
228 log = f->elogbuf;
229 t = f->curtext;
231 buf = fbufalloc();
232 mod = FALSE;
234 owner = 0;
235 if(t->w){
236 owner = t->w->owner;
237 if(owner == 0)
238 t->w->owner = 'E';
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 */
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 */
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;
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);
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);
284 if(t->q0 == b.q0 && t->q1 == b.q0)
285 t->q1 += b.nr;
286 break;
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);
296 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
297 textdelete(t, tq0, tq1, TRUE);
298 break;
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);
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);
317 if(t->q0 == b.q0 && t->q1 == b.q0)
318 t->q1 += b.nr;
319 break;
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 */
336 bufdelete(log, up, log->nc);
338 fbuffree(buf);
339 elogterm(f);
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);
352 if(t->w)
353 t->w->owner = owner;