summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exec.zig3
-rw-r--r--src/ninep/cols.zig29
2 files changed, 30 insertions, 2 deletions
diff --git a/src/exec.zig b/src/exec.zig
index 8c062f66..5b0b0250 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -346,7 +346,8 @@ pub fn spawnV9fsTty(p: *Pardes, from: usize) void {
}
fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane {
- if (p.panes[from] == null) return null;
+ // No pane asking (an empty window's tag): the session's directory, as
+ // New's scratch is.
// A directory that is not there makes no terminal: its shell could not
// start in it, and a dead pane would answer no run. Asked only of a host
// that starts shells (none, as in the core's tests, starts none).
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));