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 } 354