elog.c (7458B)
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 16 static char Wsequence[] = "warning: changes out of sequence\n"; 17 static 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 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 }; 37 38 enum 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 */ 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 }; 52 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 } 65 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 } 75 76 void 77 elogreset(File *f) 78 { 79 f->elog.type = Null; 80 f->elog.nd = 0; 81 f->elog.nr = 0; 82 } 83 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 } 95 96 void 97 elogflush(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 123 void 124 elogreplace(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 160 void 161 eloginsert(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 193 void 194 elogdelete(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 216 void 217 elogapply(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 }