ref: 7ae7351ace73a70cbbaf5a2da016005485d0d43b
dir: /appl/acme/elog.b/
implement Editlog; include "common.m"; sys: Sys; utils: Utils; buffm: Bufferm; filem: Filem; textm: Textm; edit: Edit; sprint, fprint: import sys; FALSE, TRUE, BUFSIZE, Empty, Null, Delete, Insert, Replace, Filename, Astring: import Dat; File: import filem; Buffer: import buffm; Text: import textm; error, warning, stralloc, strfree: import utils; editerror: import edit; init(mods : ref Dat->Mods) { sys = mods.sys; utils = mods.utils; buffm = mods.bufferm; filem = mods.filem; textm = mods.textm; edit = mods.edit; } Wsequence := "warning: changes out of sequence\n"; warned := FALSE; # # Log of changes made by editing commands. Three reasons for this: # 1) We want addresses in commands to apply to old file, not file-in-change. # 2) It's difficult to track changes correctly as things move, e.g. ,x m$ # 3) This gives an opportunity to optimize by merging adjacent changes. # It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a # separate implementation. To do this well, we use Replace as well as # Insert and Delete # Buflog: adt{ typex: int; # Replace, Filename q0: int; # location of change (unused in f) nd: int; # runes to delete nr: int; # runes in string or file name }; Buflogsize: con 7; SHM : con 16rffff; pack(b: Buflog) : string { a := "0123456"; a[0] = b.typex; a[1] = b.q0&SHM; a[2] = (b.q0>>16)&SHM; a[3] = b.nd&SHM; a[4] = (b.nd>>16)&SHM; a[5] = b.nr&SHM; a[6] = (b.nr>>16)&SHM; return a; } scopy(s1: ref Astring, m: int, s2: string, n: int, o: int) { p := o-n; for(i := 0; i < p; i++) s1.s[m++] = s2[n++]; } # # Minstring shouldn't be very big or we will do lots of I/O for small changes. # Maxstring is BUFSIZE so we can fbufalloc() once and not realloc elog.r. # Minstring: con 16; # distance beneath which we merge changes Maxstring: con BUFSIZE; # maximum length of change we will merge into one eloginit(f: ref File) { if(f.elog.typex != Empty) return; f.elog.typex = Null; if(f.elogbuf == nil) f.elogbuf = buffm->newbuffer(); # f.elogbuf = ref Buffer; if(f.elog.r == nil) f.elog.r = stralloc(BUFSIZE); f.elogbuf.reset(); } elogclose(f: ref File) { if(f.elogbuf != nil){ f.elogbuf.close(); f.elogbuf = nil; } } elogreset(f: ref File) { f.elog.typex = Null; f.elog.nd = 0; f.elog.nr = 0; } elogterm(f: ref File) { elogreset(f); if(f.elogbuf != nil) f.elogbuf.reset(); f.elog.typex = Empty; if(f.elog.r != nil){ strfree(f.elog.r); f.elog.r = nil; } warned = FALSE; } elogflush(f: ref File) { b: Buflog; b.typex = f.elog.typex; b.q0 = f.elog.q0; b.nd = f.elog.nd; b.nr = f.elog.nr; case(f.elog.typex){ * => warning(nil, sprint("unknown elog type 0x%ux\n", f.elog.typex)); break; Null => break; Insert or Replace => if(f.elog.nr > 0) f.elogbuf.insert(f.elogbuf.nc, f.elog.r.s, f.elog.nr); f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize); break; Delete => f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize); break; } elogreset(f); } elogreplace(f: ref File, q0: int, q1: int, r: string, nr: int) { gap: int; if(q0==q1 && nr==0) return; eloginit(f); if(f.elog.typex!=Null && q0<f.elog.q0){ if(warned++ == 0) warning(nil, Wsequence); elogflush(f); } # try to merge with previous gap = q0 - (f.elog.q0+f.elog.nd); # gap between previous and this if(f.elog.typex==Replace && f.elog.nr+gap+nr<Maxstring){ if(gap < Minstring){ if(gap > 0){ f.buf.read(f.elog.q0+f.elog.nd, f.elog.r, f.elog.nr, gap); f.elog.nr += gap; } f.elog.nd += gap + q1-q0; scopy(f.elog.r, f.elog.nr, r, 0, nr); f.elog.nr += nr; return; } } elogflush(f); f.elog.typex = Replace; f.elog.q0 = q0; f.elog.nd = q1-q0; f.elog.nr = nr; if(nr > BUFSIZE) editerror(sprint("internal error: replacement string too large(%d)", nr)); scopy(f.elog.r, 0, r, 0, nr); } eloginsert(f: ref File, q0: int, r: string, nr: int) { n: int; if(nr == 0) return; eloginit(f); if(f.elog.typex!=Null && q0<f.elog.q0){ if(warned++ == 0) warning(nil, Wsequence); elogflush(f); } # try to merge with previous if(f.elog.typex==Insert && q0==f.elog.q0 && f.elog.nr+nr<Maxstring){ ofer := f.elog.r; f.elog.r = stralloc(f.elog.nr+nr); scopy(f.elog.r, 0, ofer.s, 0, f.elog.nr); scopy(f.elog.r, f.elog.nr, r, 0, nr); f.elog.nr += nr; strfree(ofer); return; } while(nr > 0){ elogflush(f); f.elog.typex = Insert; f.elog.q0 = q0; n = nr; if(n > BUFSIZE) n = BUFSIZE; f.elog.nr = n; scopy(f.elog.r, 0, r, 0, n); r = r[n:]; nr -= n; } } elogdelete(f: ref File, q0: int, q1: int) { if(q0 == q1) return; eloginit(f); if(f.elog.typex!=Null && q0<f.elog.q0+f.elog.nd){ if(warned++ == 0) warning(nil, Wsequence); elogflush(f); } # try to merge with previous if(f.elog.typex==Delete && f.elog.q0+f.elog.nd==q0){ f.elog.nd += q1-q0; return; } elogflush(f); f.elog.typex = Delete; f.elog.q0 = q0; f.elog.nd = q1-q0; } elogapply(f: ref File) { b: Buflog; buf: ref Astring; i, n, up, mod : int; log: ref Buffer; elogflush(f); log = f.elogbuf; t := f.curtext; a := stralloc(Buflogsize); buf = stralloc(BUFSIZE); mod = FALSE; # # The edit commands have already updated the selection in t.q0, t.q1. # The text.insert and text.delete calls below will update it again, so save the # current setting and restore it at the end. # q0 := t.q0; q1 := t.q1; while(log.nc > 0){ up = log.nc-Buflogsize; log.read(up, a, 0, Buflogsize); b.typex = a.s[0]; b.q0 = a.s[1]|(a.s[2]<<16); b.nd = a.s[3]|(a.s[4]<<16); b.nr = a.s[5]|(a.s[6]<<16); case(b.typex){ * => error(sprint("elogapply: 0x%ux\n", b.typex)); break; Replace => if(!mod){ mod = TRUE; f.mark(); } # if(b.nd == b.nr && b.nr <= BUFSIZE){ # up -= b.nr; # log.read(up, buf, 0, b.nr); # t.replace(b.q0, b.q0+b.nd, buf.s, b.nr, TRUE, 0); # break; # } t.delete(b.q0, b.q0+b.nd, TRUE); up -= b.nr; for(i=0; i<b.nr; i+=n){ n = b.nr - i; if(n > BUFSIZE) n = BUFSIZE; log.read(up+i, buf, 0, n); t.insert(b.q0+i, buf.s, n, TRUE, 0); } # t.q0 = b.q0; # t.q1 = b.q0+b.nr; break; Delete => if(!mod){ mod = TRUE; f.mark(); } t.delete(b.q0, b.q0+b.nd, TRUE); # t.q0 = b.q0; # t.q1 = b.q0; break; Insert => if(!mod){ mod = TRUE; f.mark(); } up -= b.nr; for(i=0; i<b.nr; i+=n){ n = b.nr - i; if(n > BUFSIZE) n = BUFSIZE; log.read(up+i, buf, 0, n); t.insert(b.q0+i, buf.s, n, TRUE, 0); } # t.q0 = b.q0; # t.q1 = b.q0+b.nr; break; # Filename => # f.seq = u.seq; # f.unsetname(epsilon); # f.mod = u.mod; # up -= u.n; # if(u.n == 0) # f.name = nil; # else{ # fn0 := stralloc(u.n); # delta.read(up, fn0, 0, u.n); # f.name = fn0.s; # strfree(fn0); # } # break; # } log.delete(up, log.nc); } strfree(buf); strfree(a); elogterm(f); t.q0 = q0; t.q1 = q1; if(t.q1 > f.buf.nc) # can't happen t.q1 = f.buf.nc; }