diff options
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/cols.zig | 43 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 2 |
2 files changed, 40 insertions, 5 deletions
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig index efb62cfe..7f78522a 100644 --- a/src/ninep/cols.zig +++ b/src/ninep/cols.zig @@ -168,6 +168,12 @@ pub fn writeExec(p: *Pardes, req: Req, serial: ?u32) Reply { if (line.len == 0) continue; for (line) |c| if (c < ' ' and c != '\t') return tree.failText(req.tag, E.INVAL, pardes.ctlfs.ctl.e_control); if (pardes.ctlfs.ctl.tooLong(req, line)) |refusal| return refusal; + // A pane's word (Undo, Msg, Save) is no word of a tag no pane owns: + // refused as the root's ctl refuses it, never done at whichever + // pane has the keyboard. The tag's own words (New, Tty, Find, Grep + // in a column's) are its to run. + 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); p.exec_column = col; @@ -202,6 +208,34 @@ test "New written to a column's ctl reads back the new pane alone, as its exec d try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{p.panes[p.active].?.serial}), th.rd(p, root_exec, 0, 64).bytes); } +/// Whether `line` names a builtin that acts on a pane and is not one of +/// `own`, the words its tag holds by default. Edit, which the root's ctl +/// takes at the active pane, is taken here too. +fn paneWord(line: []const u8, own: []const u8) bool { + const cmd = pardes.exec.commandText(line); + 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 (b == .Edit or pardes.builtins.registry.scope(b) == .session) return false; + var words = std.mem.tokenizeScalar(u8, own, ' '); + while (words.next()) |w| if (std.mem.eql(u8, w, word)) return false; + return true; +} + +test "a pane's word written to /tagexec or a column's exec is refused, and acts on no pane" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const col_exec = tree.Node.ofCol(layout.columnSerial(p, 0), .exec); + for ([_]u64{ @intFromEnum(tree.TopFile.tagexec), col_exec }) |node| { + for ([_][]const u8{ "Undo\n", "Msg hi\n", "Redo\n" }) |line| { + const refused = th.wr(p, node, line); + try testing.expectEqual(E.INVAL, refused.errno()); + try testing.expectStringStartsWith(refused.reply.ename, "not a session control message"); + } + } + // The column tag's own words still run there. + try testing.expectEqual(tree.Status.ok, th.wr(p, col_exec, "New\n").reply.status); +} + /// rmdir of a column closes it when it is empty; one with panes is /// refused, saying so (close them, or Delcol on its ctl, which asks first). pub fn remove(p: *Pardes, req: Req, serial: u32, file: tree.ColFile) Reply { @@ -336,8 +370,10 @@ test "tagexec runs a word as a click in the workspace tag; exec files read back try testing.expectEqual(tree.Status.ok, th.look_up(p, tree.root, "tagexec").reply.status); try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, "Newcol\n").reply.status); try testing.expectEqual(@as(usize, 2), p.ncol); - try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, "Msg from the top\n").reply.status); - try testing.expect(th.logHas(p, "msg - from the top\n")); + // What it says is the session's (Newcol announcing itself); a pane's + // word such as Msg is refused there. + try testing.expect(th.logHas(p, "msg - Newcol\n")); + try testing.expectEqual(E.INVAL, th.wr(p, tagexec, "Msg from the top\n").errno()); // A column's exec makes a pane there, reads it back, and its Msg is // the session's too. const right = layout.columnSerial(p, 1); @@ -346,8 +382,7 @@ test "tagexec runs a word as a click in the workspace tag; exec files read back var want: [16]u8 = undefined; try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), th.rd(p, Node.ofCol(right, .exec), 0, 64).bytes); try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), th.rd(p, tagexec, 0, 64).bytes); - try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(right, .exec), "Msg from a column\n").reply.status); - try testing.expect(th.logHas(p, "msg - from a column\n")); + try testing.expect(th.logHas(p, "msg - New\n")); } test "a word run from the workspace's or a column's tag runs in the session's directory, a pane's in its own" { diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 9cf97efc..eea033ed 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -365,7 +365,7 @@ fn refuse(p: *Pardes, req: Req, why: []const u8, line: []const u8) Reply { } /// `refuse`, saying which ctl takes the message instead. -fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []const u8) Reply { +pub fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []const u8) Reply { const room = p.fs.ename.len -| (why.len + ctl.len + 18); const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\": write it to {s}", .{ why, line[0..@min(line.len, room)], ctl }) catch why; return tree.failText(req.tag, E.INVAL, text); |
