From cf45be79a5ab96d3b81078e7e5e2ba58bd5e5cee Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 17:25:41 -0300 Subject: Undo and Redo are builtins, as in acme's tags, so a pane's ctl and exec can step its body Over 9P there was no way to take back an edit: acme's Undo and Redo were keys only. They are now pane builtins, reached from a tag, a pane ctl and exec, and listed in /commands. They are not added to the default tags, which would change every tag. Co-Authored-By: Claude Opus 5.5 --- docs/fs.md | 3 ++- docs/tags.md | 4 ++++ src/builtins.zig | 14 ++++++++++++++ src/config.zig | 2 ++ src/ninep/ctl.zig | 15 ++++++++++++++- test/snapshots/builtins.snap | 2 ++ 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/fs.md b/docs/fs.md index 445aa725..95c1871e 100644 --- a/docs/fs.md +++ b/docs/fs.md @@ -144,7 +144,8 @@ takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir` bare for the default directory, `LocationsConfig ...`), so writing what it reads back changes nothing; platform and startup facts are `/status`'s and the Config window's, not settings. A pane's `ctl` takes the builtins that act on -a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `Find pat`) +a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `Undo` and +`Redo`, which step its body through its edits, `Find pat`) beside acme's `get`, `lock` and `unlock`. The column words are pane words too, acting on the column that pane is in: `Delcol`, `DelAbove`, `DelBelow` and the focus moves `Left`/`Right`/`Up`/`Down` from it. `Joincol` and diff --git a/docs/tags.md b/docs/tags.md index f1e4a420..de540c91 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -43,6 +43,10 @@ Its text and terminal process are kept; the command stays available in the visible tag. Every default pane tag includes `Collapse`, including file, terminal, image, and PDF panes. +`Undo` and `Redo` are acme's: typed or clicked in a pane's tag, or written +to its `ctl`, they step the body back and forward through its edits, as the +`u` and `U` keys do. They are not in the default tags. + `Del` closes a pane and gives its rows to one neighbor; the rest of the column keeps its heights. `Del k` (or `DelAbove`) gives them to the nearest expanded pane above, `Del j` (or `DelBelow`) to the one below, each falling diff --git a/src/builtins.zig b/src/builtins.zig index 23d853c8..a755969b 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -850,6 +850,20 @@ pub const Togglettymode = struct { } }; +/// acme's Undo and Redo (exec.c `undo`): step the pane's body back or +/// forward through its edits, from a tag, a pane ctl or exec. +pub const Undo = struct { + pub fn run(c: Ctx) void { + edit.doUndo(c.p, &c.pane.body); + } +}; + +pub const Redo = struct { + pub fn run(c: Ctx) void { + edit.doRedo(c.p, &c.pane.body); + } +}; + /// Fold a pane to its tag, or restore its share of the column. pub const Collapse = struct { pub fn run(c: Ctx) void { diff --git a/src/config.zig b/src/config.zig index 186b29a1..416754a6 100644 --- a/src/config.zig +++ b/src/config.zig @@ -49,6 +49,8 @@ pub const leader_path = paths: { .Mode = null, .Togglettymode = null, .Collapse = null, + .Undo = null, + .Redo = null, .Repl = null, .Kill = null, .Exit = null, diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index a64521e7..e4e983a8 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -542,10 +542,23 @@ test "commands lists every builtin with its argument and the ctl that takes it" try testing.expect(words.next() == null); } try testing.expectEqual(std.enums.values(Builtin).len, lines); - for ([_][]const u8{ "\nDel arg pane\n", "\nNewcol root\n", "\nSave arg pane\n", "\nVerbose arg root\n" }) |row| + for ([_][]const u8{ "\nDel arg pane\n", "\nNewcol root\n", "\nSave arg pane\n", "\nVerbose arg root\n", "\nUndo pane\n", "\nRedo pane\n" }) |row| try testing.expect(std.mem.indexOf(u8, listed.bytes, row) != null); } +test "Undo and Redo on a pane's ctl step its body through its edits" { + const p = try withFile(testing.allocator, "one\n"); + defer p.deinit(); + const serial = serialOf(p); + const body = Node.of(serial, .body); + try testing.expectEqual(Status.ok, wr(p, body, "two\n").reply.status); + try testing.expectEqualStrings("one\ntwo\n", rd(p, body, 0, 64).bytes); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status); + try testing.expectEqualStrings("one\n", rd(p, body, 0, 64).bytes); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Redo").reply.status); + try testing.expectEqualStrings("one\ntwo\n", rd(p, body, 0, 64).bytes); +} + test "focus reads the pane with the keyboard, and a serial written to it moves the keyboard there" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); diff --git a/test/snapshots/builtins.snap b/test/snapshots/builtins.snap index 84de680f..c9bbd2a1 100644 --- a/test/snapshots/builtins.snap +++ b/test/snapshots/builtins.snap @@ -27,6 +27,8 @@ wheel up 110 50 wheel up 110 50 wheel up 110 50 wheel up 110 50 +wheel up 110 50 +wheel up 110 50 wait 10000 Look enter, right-click wait 10000 Exec tab, middle-click wait 10000 Filter -- cgit v1.3