diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 11:44:27 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | f7c22573915cd4ecb98ef400b0914949ed07874e (patch) | |
| tree | 125f30f2338dc04d73b7b309b6a6c834927f4f9f /src/ninep | |
| parent | e61cc19e5d60403f721338f61e62a2e5569c9846 (diff) | |
| download | pardes-f7c22573915cd4ecb98ef400b0914949ed07874e.tar.gz pardes-f7c22573915cd4ecb98ef400b0914949ed07874e.zip | |
New and Tty run at /tagexec and a column's exec, and with no pane at all
The words New and Tty make a pane in the session's directory, and fs.md
already has them run at a tag no pane owns. But /tagexec refused New as a
pane's word, since the workspace tag does not hold it. Every header exec
also failed ENOENT when no pane was left, and Tty gave up when it had no
pane to take a directory from. Now New and Tty are every tag's words.
A header exec with no pane runs at the stand-in, as a click in the empty
window's tag does, and Tty with no pane asking starts in the session's
directory, as New's scratch does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/cols.zig | 29 |
1 files changed, 28 insertions, 1 deletions
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig index b1ca954c..4db78584 100644 --- a/src/ninep/cols.zig +++ b/src/ninep/cols.zig @@ -205,7 +205,8 @@ pub fn writeExec(p: *Pardes, req: Req, serial: ?u32) Reply { if (paneWord(line, if (serial == null) pardes.config.topbar_str else tagline.column_tag_default)) return pardes.ctlfs.ctl.refuseTo(p, req, "not a session control message", line, "pane/<n>/ctl"); const col = if (serial) |s| layout.columnBySerial(p, s) orelse return Reply.fail(req.tag, E.NOENT) else null; - if (p.panes[p.active] == null) return Reply.fail(req.tag, E.NOENT); + // With no pane at all, a word runs at the stand-in, as a click in + // the empty window's tag does (runBuiltinFrom). p.exec_column = col; defer p.exec_column = null; p.exec_header = true; @@ -260,6 +261,8 @@ fn paneWord(line: []const u8, own: []const u8) bool { const word = cmd[0 .. std.mem.indexOfAny(u8, cmd, " \t+") orelse cmd.len]; const b = std.meta.stringToEnum(pardes.builtins.registry.Builtin(), word) orelse return false; if (pardes.builtins.registry.scope(b) == .session) return false; + // A new pane is every tag's to make, in the session's directory. + if (b == .New or b == .Tty) return false; var words = std.mem.tokenizeScalar(u8, own, ' '); while (words.next()) |w| if (std.mem.eql(u8, w, word)) return false; return true; @@ -280,6 +283,29 @@ test "a pane's word written to /tagexec or a column's exec is refused, and acts try testing.expectEqual(tree.Status.ok, th.wr(p, col_exec, "New\n").reply.status); } +test "New and Tty run at /tagexec and a column's exec, with no pane at all too" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const tagexec = @intFromEnum(tree.TopFile.tagexec); + const col_exec = tree.Node.ofCol(layout.columnSerial(p, 0), .exec); + for ([_]u64{ tagexec, col_exec }) |node| for ([_][]const u8{ "New\n", "Tty\n" }) |line| { + const before = p.next_serial; + try testing.expectEqual(tree.Status.ok, th.wr(p, node, line).reply.status); + try testing.expectEqual(before + 1, p.next_serial); + }; + // Every pane gone: the workspace tag makes one all the same. + for ([_][]const u8{ "New\n", "Tty\n" }) |line| { + while (p.ncol > 0) { + const first = p.panes[p.col_panes[0][0]].?.serial; + _ = th.wr(p, tree.Node.of(first, .ctl), "delete\n"); + if (p.anyPane() == null) break; + } + try testing.expect(p.anyPane() == null); + try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, line).reply.status); + try testing.expect(p.anyPane() != null); + } +} + test "every pane word a tag no pane owns does not hold is refused there, Edit included, and the pane is untouched" { const p = try th.withFile(testing.allocator, "keep me\n"); defer p.deinit(); @@ -292,6 +318,7 @@ test "every pane word a tag no pane owns does not hold is refused there, Edit in var checked: usize = 0; for (std.enums.values(Builtin)) |b| { if (pardes.builtins.registry.scope(b) == .session) continue; + if (b == .New or b == .Tty) continue; // every tag's var owned = false; var words = std.mem.tokenizeScalar(u8, tag_file.own, ' '); while (words.next()) |w| owned = owned or std.mem.eql(u8, w, @tagName(b)); |
