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 f->elog.r = runerealloc(f->elog.r, f->elog.nr+nr); 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 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 * The textinsert and textdelete calls below will update it again, so save the 235 * current setting and restore it at the end. 236 */ 237 q0 = t->q0; 238 q1 = t->q1; 239 /* 240 * We constrain the addresses in here (with textconstrain()) because 241 * overlapping changes will generate bogus addresses. We will warn 242 * about changes out of sequence but proceed anyway; here we must 243 * keep things in range. 244 */ 245 246 while(log->nc > 0){ 247 up = log->nc-Buflogsize; 248 bufread(log, up, (Rune*)&b, Buflogsize); 249 switch(b.type){ 250 default: 251 fprint(2, "elogapply: 0x%ux\n", b.type); 252 abort(); 253 break; 254 255 case Replace: 256 if(!mod){ 257 mod = TRUE; 258 filemark(f); 259 } 260 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 261 textdelete(t, tq0, tq1, TRUE); 262 up -= b.nr; 263 for(i=0; i<b.nr; i+=n){ 264 n = b.nr - i; 265 if(n > RBUFSIZE) 266 n = RBUFSIZE; 267 bufread(log, up+i, buf, n); 268 textinsert(t, tq0+i, buf, n, TRUE); 269 } 270 break; 271 272 case Delete: 273 if(!mod){ 274 mod = TRUE; 275 filemark(f); 276 } 277 textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 278 textdelete(t, tq0, tq1, TRUE); 279 break; 280 281 case Insert: 282 if(!mod){ 283 mod = TRUE; 284 filemark(f); 285 } 286 textconstrain(t, b.q0, b.q0, &tq0, &tq1); 287 up -= b.nr; 288 for(i=0; i<b.nr; i+=n){ 289 n = b.nr - i; 290 if(n > RBUFSIZE) 291 n = RBUFSIZE; 292 bufread(log, up+i, buf, n); 293 textinsert(t, tq0+i, buf, n, TRUE); 294 } 295 break; 296 297 /* case Filename: 298 f->seq = u.seq; 299 fileunsetname(f, epsilon); 300 f->mod = u.mod; 301 up -= u.n; 302 free(f->name); 303 if(u.n == 0) 304 f->name = nil; 305 else 306 f->name = runemalloc(u.n); 307 bufread(delta, up, f->name, u.n); 308 f->nname = u.n; 309 break; 310 */ 311 } 312 bufdelete(log, up, log->nc); 313 } 314 fbuffree(buf); 315 if(warned){ 316 /* 317 * Changes were out of order, so the q0 and q1 318 * computed while generating those changes are not 319 * to be trusted. 320 */ 321 q1 = min(q1, f->nc); 322 q0 = min(q0, q1); 323 } 324 elogterm(f); 325 326 /* 327 * Bad addresses will cause bufload to crash, so double check. 328 */ 329 if(q0 > f->nc || q1 > f->nc || q0 > q1){ 330 warning(nil, "elogapply: can't happen %d %d %d\n", q0, q1, f->nc); 331 q1 = min(q1, f->nc); 332 q0 = min(q0, q1); 333 } 334 335 t->q0 = q0; 336 t->q1 = q1; 337 } 338