# # 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"); }