# Sam language and implementation taken from acme.
# This file was /appl/acme/elog.b, changed for o/x.
# Changed (read: broken for long files) to use a list of Elog entries
# because we keep edit logs in memory.
implement Samlog;
include "mods.m";
Edit, Tree, Elog, Empty, Null, Elogbuf, Insert, Replace, Delete,
Etext, seled, trees, Esel: import oxedit;
warnc: import sam;
init(d: Oxdat)
{
initmods(d->mods);
}
Wsequence := "warning: changes out of sequence\n";
warned := 0;
#
# 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
#
#
# Minstring shouldn't be very big or we will do lots of I/O for small changes.
# Maxstring just places a limit.
#
Minstring: con 16; # distance beneath which we merge changes
Maxstring: con 4*1024; # maximum length of change we will merge into one
eloginit(f: ref Edit)
{
if (f.elog == nil)
f.elog = ref Elog(Empty, 0, 0, "");
if(f.elog.typex != Empty)
return;
f.edited = 0;
f.elog.typex = Null;
nullelog: ref Elog;
nullelog = nil;
f.elogbuf = Elogbuf.new();
f.elog.r = "";
}
elogreset(f: ref Edit)
{
f.elog.typex = Null;
f.elog.nd = 0;
f.elog.r = "";
}
elogterm(f: ref Edit)
{
f.elogbuf = nil;
f.elog = nil;
warned = 0;
}
elogflush(f: ref Edit)
{
case(f.elog.typex){
* =>
error(sprint("unknown elog type 0x%ux\n", f.elog.typex));
Null =>
break;
Insert or
Replace or
Delete =>
f.elogbuf.push(f.elog);
break;
}
elogreset(f);
}
elogreplace(f: ref Edit, q0: int, q1: int, r: string)
{
gap: int;
if(q0==q1 && len r==0)
return;
eloginit(f);
if(f.elog.typex!=Null && q0<f.elog.q0){
if(warned++ == 0)
warnc <-= 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 && len f.elog.r+gap+len r<Maxstring){
if(gap < Minstring){
if(gap > 0)
f.elog.r += f.buf.gets(f.elog.q0+f.elog.nd, gap);
f.elog.nd += gap + q1-q0;
f.elog.r += r;
return;
}
}
elogflush(f);
f.elog.typex = Replace;
f.elog.q0 = q0;
f.elog.nd = q1-q0;
f.elog.r = r;
}
eloginsert(f: ref Edit, q0: int, r: string)
{
if(len r == 0)
return;
eloginit(f);
if(f.elog.typex!=Null && q0<f.elog.q0){
if(warned++ == 0)
warnc <-= Wsequence;
elogflush(f);
}
# try to merge with previous
if(f.elog.typex==Insert && q0==f.elog.q0 && len f.elog.r+len r<Maxstring){
f.elog.r += r;
return;
}
if(len r > 0){
elogflush(f);
f.elog.typex = Insert;
f.elog.q0 = q0;
f.elog.r = r;
}
}
elogdelete(f: ref Edit, 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)
warnc <-= 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 Edit): int
{
elogflush(f);
log := f.elogbuf;
#
# 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.
#
f.edited |= Esel;
q0 := f.q0;
q1 := f.q1;
mod := 0;
while( (b := log.pop()) != nil){
case(b.typex){
* =>
error(sprint("elogapply: 0x%ux\n", b.typex));
break;
Replace =>
mod++;
f.buf.del(b.nd, b.q0);
f.buf.ins(b.r, b.q0);
f.edited |= Etext;
break;
Delete =>
mod++;
f.buf.del(b.nd, b.q0);
f.edited |= Etext;
break;
Insert =>
mod++;
f.buf.ins(b.r, b.q0);
f.edited |= Etext;
break;
}
}
elogterm(f);
f.q0 = q0;
f.q1 = q1;
return mod;
}
|