1implement Editlog; 2 3include "common.m"; 4 5sys: Sys; 6utils: Utils; 7buffm: Bufferm; 8filem: Filem; 9textm: Textm; 10edit: Edit; 11 12sprint, fprint: import sys; 13FALSE, TRUE, BUFSIZE, Empty, Null, Delete, Insert, Replace, Filename, Astring: import Dat; 14File: import filem; 15Buffer: import buffm; 16Text: import textm; 17error, warning, stralloc, strfree: import utils; 18editerror: import edit; 19 20init(mods : ref Dat->Mods) 21{ 22 sys = mods.sys; 23 utils = mods.utils; 24 buffm = mods.bufferm; 25 filem = mods.filem; 26 textm = mods.textm; 27 edit = mods.edit; 28} 29 30Wsequence := "warning: changes out of sequence\n"; 31warned := FALSE; 32 33# 34# Log of changes made by editing commands. Three reasons for this: 35# 1) We want addresses in commands to apply to old file, not file-in-change. 36# 2) It's difficult to track changes correctly as things move, e.g. ,x m$ 37# 3) This gives an opportunity to optimize by merging adjacent changes. 38# It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a 39# separate implementation. To do this well, we use Replace as well as 40# Insert and Delete 41# 42 43Buflog: adt{ 44 typex: int; # Replace, Filename 45 q0: int; # location of change (unused in f) 46 nd: int; # runes to delete 47 nr: int; # runes in string or file name 48}; 49 50Buflogsize: con 7; 51SHM : con 16rffff; 52 53pack(b: Buflog) : string 54{ 55 a := "0123456"; 56 a[0] = b.typex; 57 a[1] = b.q0&SHM; 58 a[2] = (b.q0>>16)&SHM; 59 a[3] = b.nd&SHM; 60 a[4] = (b.nd>>16)&SHM; 61 a[5] = b.nr&SHM; 62 a[6] = (b.nr>>16)&SHM; 63 return a; 64} 65 66scopy(s1: ref Astring, m: int, s2: string, n: int, o: int) 67{ 68 p := o-n; 69 for(i := 0; i < p; i++) 70 s1.s[m++] = s2[n++]; 71} 72 73# 74# Minstring shouldn't be very big or we will do lots of I/O for small changes. 75# Maxstring is BUFSIZE so we can fbufalloc() once and not realloc elog.r. 76# 77Minstring: con 16; # distance beneath which we merge changes 78Maxstring: con BUFSIZE; # maximum length of change we will merge into one 79 80eloginit(f: ref File) 81{ 82 if(f.elog.typex != Empty) 83 return; 84 f.elog.typex = Null; 85 if(f.elogbuf == nil) 86 f.elogbuf = buffm->newbuffer(); 87 # f.elogbuf = ref Buffer; 88 if(f.elog.r == nil) 89 f.elog.r = stralloc(BUFSIZE); 90 f.elogbuf.reset(); 91} 92 93elogclose(f: ref File) 94{ 95 if(f.elogbuf != nil){ 96 f.elogbuf.close(); 97 f.elogbuf = nil; 98 } 99} 100 101elogreset(f: ref File) 102{ 103 f.elog.typex = Null; 104 f.elog.nd = 0; 105 f.elog.nr = 0; 106} 107 108elogterm(f: ref File) 109{ 110 elogreset(f); 111 if(f.elogbuf != nil) 112 f.elogbuf.reset(); 113 f.elog.typex = Empty; 114 if(f.elog.r != nil){ 115 strfree(f.elog.r); 116 f.elog.r = nil; 117 } 118 warned = FALSE; 119} 120 121elogflush(f: ref File) 122{ 123 b: Buflog; 124 125 b.typex = f.elog.typex; 126 b.q0 = f.elog.q0; 127 b.nd = f.elog.nd; 128 b.nr = f.elog.nr; 129 case(f.elog.typex){ 130 * => 131 warning(nil, sprint("unknown elog type 0x%ux\n", f.elog.typex)); 132 break; 133 Null => 134 break; 135 Insert or 136 Replace => 137 if(f.elog.nr > 0) 138 f.elogbuf.insert(f.elogbuf.nc, f.elog.r.s, f.elog.nr); 139 f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize); 140 break; 141 Delete => 142 f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize); 143 break; 144 } 145 elogreset(f); 146} 147 148elogreplace(f: ref File, q0: int, q1: int, r: string, nr: int) 149{ 150 gap: int; 151 152 if(q0==q1 && nr==0) 153 return; 154 eloginit(f); 155 if(f.elog.typex!=Null && q0<f.elog.q0){ 156 if(warned++ == 0) 157 warning(nil, Wsequence); 158 elogflush(f); 159 } 160 # try to merge with previous 161 gap = q0 - (f.elog.q0+f.elog.nd); # gap between previous and this 162 if(f.elog.typex==Replace && f.elog.nr+gap+nr<Maxstring){ 163 if(gap < Minstring){ 164 if(gap > 0){ 165 f.buf.read(f.elog.q0+f.elog.nd, f.elog.r, f.elog.nr, gap); 166 f.elog.nr += gap; 167 } 168 f.elog.nd += gap + q1-q0; 169 scopy(f.elog.r, f.elog.nr, r, 0, nr); 170 f.elog.nr += nr; 171 return; 172 } 173 } 174 elogflush(f); 175 f.elog.typex = Replace; 176 f.elog.q0 = q0; 177 f.elog.nd = q1-q0; 178 f.elog.nr = nr; 179 if(nr > BUFSIZE) 180 editerror(sprint("internal error: replacement string too large(%d)", nr)); 181 scopy(f.elog.r, 0, r, 0, nr); 182} 183 184eloginsert(f: ref File, q0: int, r: string, nr: int) 185{ 186 n: int; 187 188 if(nr == 0) 189 return; 190 eloginit(f); 191 if(f.elog.typex!=Null && q0<f.elog.q0){ 192 if(warned++ == 0) 193 warning(nil, Wsequence); 194 elogflush(f); 195 } 196 # try to merge with previous 197 if(f.elog.typex==Insert && q0==f.elog.q0 && f.elog.nr+nr<Maxstring){ 198 ofer := f.elog.r; 199 f.elog.r = stralloc(f.elog.nr+nr); 200 scopy(f.elog.r, 0, ofer.s, 0, f.elog.nr); 201 scopy(f.elog.r, f.elog.nr, r, 0, nr); 202 f.elog.nr += nr; 203 strfree(ofer); 204 return; 205 } 206 while(nr > 0){ 207 elogflush(f); 208 f.elog.typex = Insert; 209 f.elog.q0 = q0; 210 n = nr; 211 if(n > BUFSIZE) 212 n = BUFSIZE; 213 f.elog.nr = n; 214 scopy(f.elog.r, 0, r, 0, n); 215 r = r[n:]; 216 nr -= n; 217 } 218} 219 220elogdelete(f: ref File, q0: int, q1: int) 221{ 222 if(q0 == q1) 223 return; 224 eloginit(f); 225 if(f.elog.typex!=Null && q0<f.elog.q0+f.elog.nd){ 226 if(warned++ == 0) 227 warning(nil, Wsequence); 228 elogflush(f); 229 } 230 # try to merge with previous 231 if(f.elog.typex==Delete && f.elog.q0+f.elog.nd==q0){ 232 f.elog.nd += q1-q0; 233 return; 234 } 235 elogflush(f); 236 f.elog.typex = Delete; 237 f.elog.q0 = q0; 238 f.elog.nd = q1-q0; 239} 240 241elogapply(f: ref File) 242{ 243 b: Buflog; 244 buf: ref Astring; 245 i, n, up, mod : int; 246 log: ref Buffer; 247 248 elogflush(f); 249 log = f.elogbuf; 250 t := f.curtext; 251 252 a := stralloc(Buflogsize); 253 buf = stralloc(BUFSIZE); 254 mod = FALSE; 255 256 # 257 # The edit commands have already updated the selection in t.q0, t.q1. 258 # The text.insert and text.delete calls below will update it again, so save the 259 # current setting and restore it at the end. 260 # 261 q0 := t.q0; 262 q1 := t.q1; 263 264 while(log.nc > 0){ 265 up = log.nc-Buflogsize; 266 log.read(up, a, 0, Buflogsize); 267 b.typex = a.s[0]; 268 b.q0 = a.s[1]|(a.s[2]<<16); 269 b.nd = a.s[3]|(a.s[4]<<16); 270 b.nr = a.s[5]|(a.s[6]<<16); 271 case(b.typex){ 272 * => 273 error(sprint("elogapply: 0x%ux\n", b.typex)); 274 break; 275 276 Replace => 277 if(!mod){ 278 mod = TRUE; 279 f.mark(); 280 } 281 # if(b.nd == b.nr && b.nr <= BUFSIZE){ 282 # up -= b.nr; 283 # log.read(up, buf, 0, b.nr); 284 # t.replace(b.q0, b.q0+b.nd, buf.s, b.nr, TRUE, 0); 285 # break; 286 # } 287 t.delete(b.q0, b.q0+b.nd, TRUE); 288 up -= b.nr; 289 for(i=0; i<b.nr; i+=n){ 290 n = b.nr - i; 291 if(n > BUFSIZE) 292 n = BUFSIZE; 293 log.read(up+i, buf, 0, n); 294 t.insert(b.q0+i, buf.s, n, TRUE, 0); 295 } 296 # t.q0 = b.q0; 297 # t.q1 = b.q0+b.nr; 298 break; 299 300 Delete => 301 if(!mod){ 302 mod = TRUE; 303 f.mark(); 304 } 305 t.delete(b.q0, b.q0+b.nd, TRUE); 306 # t.q0 = b.q0; 307 # t.q1 = b.q0; 308 break; 309 310 Insert => 311 if(!mod){ 312 mod = TRUE; 313 f.mark(); 314 } 315 up -= b.nr; 316 for(i=0; i<b.nr; i+=n){ 317 n = b.nr - i; 318 if(n > BUFSIZE) 319 n = BUFSIZE; 320 log.read(up+i, buf, 0, n); 321 t.insert(b.q0+i, buf.s, n, TRUE, 0); 322 } 323 # t.q0 = b.q0; 324 # t.q1 = b.q0+b.nr; 325 break; 326 327# Filename => 328# f.seq = u.seq; 329# f.unsetname(epsilon); 330# f.mod = u.mod; 331# up -= u.n; 332# if(u.n == 0) 333# f.name = nil; 334# else{ 335# fn0 := stralloc(u.n); 336# delta.read(up, fn0, 0, u.n); 337# f.name = fn0.s; 338# strfree(fn0); 339# } 340# break; 341# 342 } 343 log.delete(up, log.nc); 344 } 345 strfree(buf); 346 strfree(a); 347 elogterm(f); 348 349 t.q0 = q0; 350 t.q1 = q1; 351 if(t.q1 > f.buf.nc) # can't happen 352 t.q1 = f.buf.nc; 353} 354