summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 20:50:48 -0300
committerGabriel Schneider <[email protected]>2026-10-01 22:07:48 -0300
commit406f483288c7689bdd2e1f3f68b276c61ca9412a (patch)
treeb6398fb9974b91441563fd655d18ddaf182a2a5a /src/ninep/ctl.zig
parentb55cb01d2777760ed79b545dfca7115837591577 (diff)
downloadpardes-406f483288c7689bdd2e1f3f68b276c61ca9412a.tar.gz
pardes-406f483288c7689bdd2e1f3f68b276c61ca9412a.zip
An Edit's held write: < | > commands no longer block their 9P connection (answered like a held read, so a status read or a filter reading the session's own mount runs beside it), a Tflush or hang-up of the write kills the commands' process groups and changes nothing; e loads by Get's way (asked once, clean after, undo puts the name back); ~ in e r w f B; B checks every name first; an Edit that runs commands is a write of its own, refused up front with other lines; Edit's +Errors output keeps the keyboard where it was; X goes in pane order; a refused write open says its errno; the reference's Coming from acme rows say what Get file's undo, failure and directory refusal, Putall's answer, Zerox across Dump and Incl's directories now do
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig27
1 files changed, 21 insertions, 6 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 274caf88..8ece7ead 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -193,6 +193,7 @@ pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Rep
// The root's lines run at the keyboard's pane: a refusal before any runs
// is that pane's too, in its err record.
p.fs.ran_at = if (serial == null) (if (p.panes[p.active]) |pane| pane.serial else 0) else 0;
+ if (editNotAlone(p, req.data)) return tree.failText(req.tag, E.INVAL, e_after_edit);
for ([2]bool{ false, true }) |apply| {
if (apply) p.fs.results_len = 0;
var it = Messages.init(p, req.data);
@@ -203,7 +204,6 @@ pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Rep
for (text) |c| if ((c < ' ' and c != '\t' and c != '\n') or c == 0x7f) return tree.failText(req.tag, E.INVAL, e_control);
if (exec) if (tooLong(req, text)) |refusal| return refusal;
if (!apply) continue;
- if (p.fs.edit_answer_at != null) return tree.failText(req.tag, E.INVAL, e_after_edit);
var id = if (serial) |s| p.paneBySerial(s) orelse break else p.active;
// No pane at all: the root's look and exec act in the session's
// directory. An exec runs as the workspace tag's; a look from a
@@ -260,10 +260,25 @@ pub fn tooLong(req: Req, line: []const u8) ?Reply {
pub const e_control = "invalid command line: it holds a control character (or DEL) other than a tab";
-/// An Edit whose `<`, `|` or `>` commands are running answers its write
-/// when they are done (edit_cmd.zig); a line after it in the same write
-/// would run before them, on the text they are about to change.
-pub const e_after_edit = "invalid command line: an Edit running commands ends its write; write what follows after it";
+/// An Edit whose `<`, `|` or `>` commands run answers its write when they
+/// are done (edit_cmd.zig); a line after it in the same write would run
+/// before them, on the text they are about to change. Refused up front,
+/// before any line runs.
+pub const e_after_edit = "invalid command line: an Edit that runs commands is a write of its own";
+
+/// Whether `data` holds an Edit that runs commands and any other line.
+fn editNotAlone(p: *Pardes, data: []const u8) bool {
+ const sam = @import("../sam_edit.zig");
+ var lines: usize = 0;
+ var runs = false;
+ var it = Messages.init(p, data);
+ while (it.next()) |line| {
+ if (line.len == 0 or exec_line.isComment(line)) continue;
+ lines += 1;
+ if (std.mem.startsWith(u8, line, "Edit") and sam.runsCommands(p.scratch.allocator(), line["Edit".len..])) runs = true;
+ }
+ return runs and lines > 1;
+}
/// Runs one click (`run`) as a 9P write's: a builtin that fails there fails
/// the write, with its words and an err record, and no msg, as a ctl line
@@ -959,13 +974,13 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
const serial = pane.serial;
// Checked whole before anything applies, so a write that must wait for
// the lock has done nothing yet when it goes again.
+ if (editNotAlone(p, req.data)) return tree.failText(req.tag, E.INVAL, e_after_edit);
for ([2]bool{ false, true }) |apply| {
if (apply) p.fs.results_len = 0;
var held = !other and pane.fs.lock != null;
var it = Messages.init(p, req.data);
while (it.next()) |line_in| {
if (line_in.len == 0 or exec_line.isComment(line_in)) continue;
- if (apply and p.fs.edit_answer_at != null) return tree.failText(req.tag, E.INVAL, e_after_edit);
// A line before this one may have closed the pane (`Del`).
if (apply and p.paneBySerial(serial) == null) return Reply.fail(req.tag, E.NOENT);
// acme's ctl words: done by the file that replaces each, or the