Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/octopus/port/live/tundo.b

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


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

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.