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 && (q0+nr)-f->elog.q0<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 q0, q1, tq0, tq1; 222 Buffer *log; 223 Text *t; 224 225 elogflush(f); 226 log = f->elogbuf; 227 t = f->curtext; 228 229 buf = fbufalloc(); 230 mod = FALSE; 231 232 /* 233 * The edit commands have already updated the selection in t->q0, t->q1. 234 * (At least, they are supposed to have updated them. 235 * We still keep finding commands that don't do it right.) 236 * The textinsert and textdelete calls below will update it again, so save the 237 * current setting and restore it at the end. 238 */ 239 q0 = t->q0; 240 q1 = t->q1; 241 /* 242 * We constrain the addresses in here (with textconstrain()) because 243 * overlapping changes will generate bogus addresses. We will warn 244 * about changes out of sequence but proceed anyway; here we must 245 * keep things in range. 246 */ 247 248 while(log->nc > 0){ 249 up = log->nc-Buflogsize; 250 bufread(log, up, (Rune*)&b, Buflogsize); 251 switch(b.type){ 252 default: 253 fprint(2, "elogapply: 0x%ux\n", b.type); 254 abort(); 255 break; 256 257 case Replace: 258 if(tracelog) 259 warning(nil, "elog replace %d %d\n", 260 b.q0, b.q0+b.nd); 261 if(!mod){ 262 mod = TRUE; 263 filemark(f); 264 } 265 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 266 textdelete(t, tq0, tq1, TRUE); 267 up -= b.nr; 268 for(i=0; i<b.nr; i+=n){ 269 n = b.nr - i; 270 if(n > RBUFSIZE) 271 n = RBUFSIZE; 272 bufread(log, up+i, buf, n); 273 textinsert(t, tq0+i, buf, n, TRUE); 274 } 275 break; 276 277 case Delete: 278 if(tracelog) 279 warning(nil, "elog delete %d %d\n", 280 b.q0, b.q0+b.nd); 281 if(!mod){ 282 mod = TRUE; 283 filemark(f); 284 } 285 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 286 textdelete(t, tq0, tq1, TRUE); 287 break; 288 289 case Insert: 290 if(tracelog) 291 warning(nil, "elog insert %d %d\n", 292 b.q0, b.q0+b.nr); 293 if(!mod){ 294 mod = TRUE; 295 filemark(f); 296 } 297 textconstrain(t, b.q0, b.q0, &tq0, &tq1); 298 up -= b.nr; 299 for(i=0; i<b.nr; i+=n){ 300 n = b.nr - i; 301 if(n > RBUFSIZE) 302 n = RBUFSIZE; 303 bufread(log, up+i, buf, n); 304 textinsert(t, tq0+i, buf, n, TRUE); 305 } 306 break; 307 308 /* case Filename: 309 f->seq = u.seq; 310 fileunsetname(f, epsilon); 311 f->mod = u.mod; 312 up -= u.n; 313 free(f->name); 314 if(u.n == 0) 315 f->name = nil; 316 else 317 f->name = runemalloc(u.n); 318 bufread(delta, up, f->name, u.n); 319 f->nname = u.n; 320 break; 321 */ 322 } 323 bufdelete(log, up, log->nc); 324 } 325 fbuffree(buf); 326 if(warned){ 327 /* 328 * Changes were out of order, so the q0 and q1 329 * computed while generating those changes are not 330 * to be trusted. 331 */ 332 q1 = min(q1, f->nc); 333 q0 = min(q0, q1); 334 } 335 elogterm(f); 336 337 /* 338 * The q0 and q1 are supposed to be fine (see comment 339 * above, where we saved them), but bad addresses 340 * will cause bufload to crash, so double check. 341 */ 342 if(q0 > f->nc || q1 > f->nc || q0 > q1){ 343 warning(nil, "elogapply: can't happen %d %d %d\n", q0, q1, f->nc); 344 q1 = min(q1, f->nc); 345 q0 = min(q0, q1); 346 } 347 348 t->q0 = q0; 349 t->q1 = q1; 350 } 351