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 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 * but using coordinates relative to the unmodified buffer. As we apply the log, 235 * we have to update the coordinates to be relative to the modified buffer. 236 * Textinsert and textdelete will do this for us; our only work is to apply the 237 * convention that an insertion at t->q0==t->q1 is intended to select the 238 * inserted text. 239 */ 240 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 (%d %d)\n", 260 b.q0, b.q0+b.nd, t->q0, t->q1); 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 if(t->q0 == b.q0 && t->q1 == b.q0) 276 t->q1 += b.nr; 277 break; 278 279 case Delete: 280 if(tracelog) 281 warning(nil, "elog delete %d %d (%d %d)\n", 282 b.q0, b.q0+b.nd, t->q0, t->q1); 283 if(!mod){ 284 mod = TRUE; 285 filemark(f); 286 } 287 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 288 textdelete(t, tq0, tq1, TRUE); 289 break; 290 291 case Insert: 292 if(tracelog) 293 warning(nil, "elog insert %d %d (%d %d)\n", 294 b.q0, b.q0+b.nr, t->q0, t->q1); 295 if(!mod){ 296 mod = TRUE; 297 filemark(f); 298 } 299 textconstrain(t, b.q0, b.q0, &tq0, &tq1); 300 up -= b.nr; 301 for(i=0; i<b.nr; i+=n){ 302 n = b.nr - i; 303 if(n > RBUFSIZE) 304 n = RBUFSIZE; 305 bufread(log, up+i, buf, n); 306 textinsert(t, tq0+i, buf, n, TRUE); 307 } 308 if(t->q0 == b.q0 && t->q1 == b.q0) 309 t->q1 += b.nr; 310 break; 311 312 /* case Filename: 313 f->seq = u.seq; 314 fileunsetname(f, epsilon); 315 f->mod = u.mod; 316 up -= u.n; 317 free(f->name); 318 if(u.n == 0) 319 f->name = nil; 320 else 321 f->name = runemalloc(u.n); 322 bufread(delta, up, f->name, u.n); 323 f->nname = u.n; 324 break; 325 */ 326 } 327 bufdelete(log, up, log->nc); 328 } 329 fbuffree(buf); 330 elogterm(f); 331 332 /* 333 * Bad addresses will cause bufload to crash, so double check. 334 * If changes were out of order, we expect problems so don't complain further. 335 */ 336 if(t->q0 > f->nc || t->q1 > f->nc || t->q0 > t->q1){ 337 if(!warned) 338 warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->nc); 339 t->q1 = min(t->q1, f->nc); 340 t->q0 = min(t->q0, t->q1); 341 } 342 } 343