#
# Edits is a list of edits for undo/redo and for syncing to the FS.
# We update the current edit as much as we can, before syncing it.
# Edits.pos points always to the "current" edit.
# A delete within a current insert is recorded by deleting the text from the insert.
# A insert (delete) expanding a current insert (delete) simply adds more text to it.
# If the current edit is synced, it's done and another edit is allocated.
# Otherwise, Edit.ins and Edit.del report failure, asking the caller to sync the edit before
# trying again.
# Any synced edit after pos corresponds to undone edits that must be synced in
# reverse.
# Only the last edit might be not synced.
implement Tundo;
include "sys.m";
sys: Sys;
fprint, sprint: import sys;
include "error.m";
err: Error;
checkload, stderr, panic, kill, error: import err;
include "tundo.m";
debug := 0;
init(sysm: Sys, e: Error, dbg: int)
{
sys = sysm;
err = e;
debug = dbg;
}
Edits.new(): ref Edits
{
ed0 := ref Edit.Del(1, 0, "");
edits := ref Edits;
edits.e = array[1] of ref Edit;
edits.e[0] = ed0;
edits.pos = edits.cpos = 0;
return edits;
}
Edits.undo(edits: self ref Edits): ref Edit
{
if (edits.pos == 0) # end of edits. null 0th edit kept.
return nil;
if (edits.e[edits.pos] == nil)
panic("edits undo bug");
return reverse(edits.e[edits.pos--]);
}
Edits.redo(edits: self ref Edits): ref Edit
{
if (edits.pos+1 >= len edits.e || edits.e[edits.pos+1] == nil)
return nil;
return edits.e[++edits.pos];
}
# Ensure we are ready to ins/del.
# After mkpos, edits.pos points to either a not synced entry or to a nil entry.
Edits.mkpos(edits: self ref Edits): int
{
if (edits.e[edits.pos] != nil && edits.e[edits.pos].synced){
for (i := edits.pos+1; i < len edits.e && edits.e[i] != nil; i++){
if (edits.e[i].synced)
return -1; # must sync these undos before
edits.e[i] = nil;
}
edits.pos++;
}
if (edits.pos >= len edits.e){
ne := array[len edits.e+3] of ref Edit;
ne[0:] = edits.e;
for(i := len edits.e; i < len ne; i++)
ne[i] = nil;
edits.e = ne;
}
return 0;
}
Edits.ins(edits: self ref Edits, s: string, pos: int): int
{
if (len s == 0){
fprint(stderr, "edits: ins: null string\n");
return 0;
}
if (edits.mkpos() < 0)
return -1;
if (edits.e[edits.pos] == nil) # can't fail later
edits.e[edits.pos] = ref Edit.Ins(0, pos, "");
pick edp := edits.e[edits.pos] {
Ins =>
if (pos < edp.pos || pos > edp.pos + len edp.s)
return -1;
l := len edp.s;
pos -= edp.pos;
if (len s == 1 && pos == l)
edp.s[l] = s[0];
else {
ns := edp.s[0:pos] + s;
if (pos < l)
ns += edp.s[pos:];
edp.s = ns;
}
Del =>
return -1;
}
return 0;
}
Edits.del(edits: self ref Edits, s: string, pos: int): int
{
if (len s == 0){
fprint(stderr, "edits: del: null string\n");
return 0;
}
if (edits.mkpos() < 0)
return -1;
if (edits.e[edits.pos] == nil) # can't fail later
edits.e[edits.pos] = ref Edit.Del(0, pos, "");
pick edp := edits.e[edits.pos] {
Ins =>
epos := pos + len s;
if (pos < edp.pos || epos > edp.pos + len edp.s)
return -1;
pos -= edp.pos;
epos-= edp.pos;
ns := edp.s[0:pos];
if (epos < len edp.s)
ns += edp.s[epos:];
edp.s = ns;
Del =>
if (pos + len s == edp.pos){
edp.pos = pos;
edp.s = s + edp.s;
} else if (pos == edp.pos + len edp.s)
edp.s += s;
else
return -1;
}
return 0;
}
reverse(ed: ref Edit): ref Edit
{
pick edp := ed {
Ins =>
return ref Edit.Del(0, edp.pos, edp.s);
Del =>
return ref Edit.Ins(0, edp.pos, edp.s);
}
return nil;
}
nulledit(ed: ref Edit): int
{
pick edp := ed {
Ins =>
return len edp.s == 0;
Del =>
return len edp.s == 0;
}
return 0;
}
# If current entry not synced, return it for syncing.
# If undone edits not synced, return them in order of syncing.
Edits.sync(edits: self ref Edits): list of ref Edit
{
if (edits.e[edits.pos] != nil){
ed := edits.e[edits.pos];
if (ed.synced){
nl : list of ref Edit;
nl = nil;
for (i := edits.pos+1; i < len edits.e && edits.e[i] != nil; i++){
if (edits.e[i].synced && !nulledit(edits.e[i]))
nl = reverse(edits.e[i]) :: nl;
edits.e[i] = nil;
}
return nl;
} else {
ed.synced++;
if (!nulledit(ed))
return list of {ed};
}
}
return nil;
}
dtxt(s: string): string
{
if (len s> 35)
s = s[0:15] + " ... " + s[len s - 15:];
ns := "";
for (i := 0; i < len s; i++)
if (s[i] == '\n')
ns += "\\n";
else
ns[len ns] = s[i];
return ns;
}
Edits.dump(edits: self ref Edits)
{
fprint(stderr, "%d edits (pos %d cpos %d)\n", len edits.e, edits.pos, edits.cpos);
for (i := 0; i < len edits.e && edits.e[i] != nil; i++){
e := edits.e[i];
s := " ";
if (i == edits.pos)
s = " ->";
if (i == edits.cpos)
s[1] = 'c';
if (e.synced)
s[0] = 's';
s = sprint("[%02d] %s\t", i, s);
pick ep := e {
Ins =>
s += sprint("ins pos %d '%s'", ep.pos, dtxt(ep.s));
Del =>
s += sprint("del pos %d '%s'", ep.pos, dtxt(ep.s));
}
fprint(stderr, "%s\n", s);
}
fprint(stderr, "\n");
}
|