ed

[hare] The standard editor
Log | Files | Refs | README | LICENSE

commit 2cd2cb220b526a5b35743ff8e505fd5d06c309eb
parent a907f4c2d1a1f39a4656c2a4a811d7eece717458
Author: Byron Torres <b@torresjrjr.com>
Date:   Tue, 16 Jan 2024 23:49:25 +0000

add cmd_undo(), history.ha, full undo history impl

Diffstat:
MMakefile | 1+
Mbuffer.ha | 20++++++++++++++++++--
Mcommand.ha | 11++++++++++-
Merror.ha | 2++
Ahistory.ha | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.ha | 5+++--
6 files changed, 88 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile @@ -10,6 +10,7 @@ source=\ execute.ha \ file.ha \ global.ha \ + history.ha \ interaction.ha \ parse.ha \ print.ha \ diff --git a/buffer.ha b/buffer.ha @@ -8,6 +8,7 @@ type Buffer = struct{ filename: str, lines: []*Line, trash: []*Line, + hist: []ChangeSet, cursor: size, modified: bool, written: bool, @@ -29,16 +30,26 @@ fn buf_insert(buf: *Buffer, n: size, ls: *Line...) void = { // for (let i = 0z; i < len(ls); i += 1) { // debug("buf_insert(): ls[{}].text='{}'", i, ls[i].text); // }; + insert(buf.lines[n], ls...); + if (buf.written) buf.modified = true; + + hist_append(buf, (n, len(ls)): Addition); + + debug("buf_insert(): len('last changeset')={}", len(buf.hist[len(buf.hist) - 1])); + +// let change = Change{ kind = true, n = n, z = len(ls) }; +// let activeset = buf.hist[buf.activehist]; +// append(activeset, change); }; fn buf_deleteall(buf: *Buffer) void = { // buf_wipetrash(buf); if (len(buf.lines) > 1) { - insert(buf.trash[0], buf.lines[1..]...); + append(buf.trash, buf.lines[1..]...); delete(buf.lines[1..]); }; if (buf.written) @@ -57,10 +68,15 @@ fn buf_delete(buf: *Buffer, a: size, b: size) void = { // buf.lines[i].globalmark = false; //}; - insert(buf.trash[0], buf.lines[a..b+1]...); + append(buf.trash, buf.lines[a..b+1]...); delete(buf.lines[a..b+1]); + if (buf.written) buf.modified = true; + + hist_append(buf, (a, (b + 1 - a)): Deletion); + + debug("buf_delete(): len('last changeset')={}", len(buf.hist[len(buf.hist) - 1])); }; fn buf_read(buf: *Buffer, src: io::handle, a: size) ((size, size) | io::error | encoding::utf8::invalid) = { diff --git a/command.ha b/command.ha @@ -44,6 +44,7 @@ type CmdError = !( | NoPrevShCmd | NoPrevGlobalSubCmd | InvalidGlobalSubCmd + | NoHistory | regex::error | fs::error | os::exec::error @@ -120,6 +121,7 @@ fn cmd_append(s: *Session, cmd: *Command) (void | Error) = { const n = get_linenum(cmd.linenums, s.buf.cursor); + hist_newset(s.buf); for (let i = 0z; i < len(cmd.textinput); i += 1) { const line = alloc(Line{ text = cmd.textinput[i], ... }); buf_insert(s.buf, n + 1 + i, line); @@ -174,6 +176,7 @@ fn cmd_delete(s: *Session, cmd: *Command) (void | Error) = { )?; assert_nonzero(s, a)?; + hist_newset(s.buf); buf_delete(s.buf, a, b); s.buf.cursor = @@ -566,7 +569,13 @@ fn cmd_copy(s: *Session, cmd: *Command) (void | Error) = { printmode(s, cmd)?; }; -fn cmd_undo(s: *Session, cmd: *Command) (void | Error) = void; +fn cmd_undo(s: *Session, cmd: *Command) (void | Error) = { + s.warned = false; + + assert_noaddrs(s, cmd.linenums)?; + + hist_undo(s.buf)?; +}; fn cmd_vglobal(s: *Session, cmd: *Command) (void | Error) = { global(s, cmd, false)?; diff --git a/error.ha b/error.ha @@ -54,6 +54,8 @@ fn strerror(err: Error) str = { return "No previous global subcommand"; case let e: InvalidGlobalSubCmd => return "Invalid interactive global subcommand"; // TODO: append 'e'? + case NoHistory => + return "Nothing to undo"; // TODO: append 'e'? case let e: regex::error => return regex::strerror(e); case let e: fs::error => diff --git a/history.ha b/history.ha @@ -0,0 +1,54 @@ +type History = []ChangeSet; + +type ChangeSet = []Change; + +type Change = (Addition | Deletion); + +type Addition = (size, size); + +type Deletion = (size, size); + +type NoHistory = !void; + +fn hist_newset(buf: *Buffer) void = { +debug("hist_newset(): begin"); + append(buf.hist, []: ChangeSet); +debug("hist_newset(): end"); +}; + +fn hist_append(buf: *Buffer, change: Change) void = { + append(buf.hist[len(buf.hist) - 1], change); +}; + +fn hist_undo(buf: *Buffer) (void | NoHistory) = { +debug("hist_undo(): len(buf.hist)={}", len(buf.hist)); + if (len(buf.hist) == 0) + return NoHistory; + + let lastset = buf.hist[ len(buf.hist) - 1 ]; +debug("hist_undo(): len(lastset)={}", len(lastset)); + + for (let i = len(lastset) - 1; i < len(lastset); i -= 1) { + let change = lastset[i]; + match (change) { + case let deln: Deletion => + let (n, m) = deln; +debug("hist_undo(): deln=({}, {})", n, m); + let lentrash = len(buf.trash); +debug("hist_undo(): lentrash={}", lentrash); + let lines = buf.trash[(lentrash - m)..]; +debug("hist_undo(): len(lines)={}", len(lines)); + insert(buf.lines[n], lines...); + delete(buf.trash[(lentrash - m)..]); + case let addn: Addition => + let (n, m) = addn; +debug("hist_undo(): addn=({}, {})", n, m); + let lines = buf.lines[n..(n + m)]; +debug("hist_undo(): len(lines)={}", len(lines)); + append(buf.trash, lines...); + delete(buf.lines[n..(n + m)]); + }; + }; + + delete(buf.hist[ len(buf.hist) - 1 ]); +}; diff --git a/main.ha b/main.ha @@ -36,8 +36,9 @@ export fn main() void = { defer bufio::finish(&input); const buffer = Buffer{ - lines = *alloc([ alloc(Line{ ... }) ]), - trash = *alloc([]: []*Line), + lines = alloc([ alloc(Line{ ... }) ]...), + trash = alloc([]: []*Line...), + hist = alloc([]: History...), ... }; defer buffer_finish(&buffer);