summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 04:56:17 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commite2452380ed42d3b418930e375780745612d599c5 (patch)
tree61ca969e4ba5df779953e7b9c06b03d09cc1af43
parent253a5c1e036d96eada910c47eb500ed0c8e37b5c (diff)
downloadpardes-e2452380ed42d3b418930e375780745612d599c5.tar.gz
pardes-e2452380ed42d3b418930e375780745612d599c5.zip
An a, c or i text with no . line fails the Edit, as an open { does
sam takes the end of input as a text block's end, so a block cut short (a write that lost its tail, an open closed early) inserted half of itself. An Edit whose a, c or i text never meets its . line now fails with "a, c or i text not ended by a . line" and changes nothing, as an unclosed { does; at a close, logged as an err. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md3
-rw-r--r--docs/fs.md6
-rw-r--r--src/ninep/tree.zig11
-rw-r--r--src/sam_edit.zig5
4 files changed, 22 insertions, 3 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index ae42bc86..e363b7cb 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -150,7 +150,8 @@ A read is a stream: once read, a second read on the same fd gives EOF (on
an open that wrote, until its next write); open again, or seek to 0, to read
it again. Each command line runs once whole, however a mount cuts a big write; a last
line with no newline runs when the open closes, and an Edit block still open
-then fails there (an `err`, ``unmatched `{'``), changing nothing. A command that fails is reported in
+then fails there (an `err`: ``unmatched `{'``, or an a/c/i text with no `.`
+line), changing nothing. A command that fails is reported in
the editor, not as a write error, so inspect the resulting pane, index, message
or screen; only a malformed line fails the write itself. A word no builtin
knows (a typo included) is a command line: written at a terminal at its
diff --git a/docs/fs.md b/docs/fs.md
index 62716cf0..f61bbbe6 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -429,8 +429,10 @@ kernel's, 8 KiB from a client that asks), anywhere, and each piece comes as
a write of its own, so the open keeps a last line with no newline yet, or
an Edit block whose text has not ended, until its next write, and runs it
then; what is left when it closes runs at the close, where an Edit block
-whose `{` or text never ended fails as any unclosed block does (``unmatched
-`{'``, logged as an `err`; nothing changes). A fragment never runs on its
+whose `{` or `a`/`c`/`i` text never ended fails and changes nothing
+(``unmatched `{'``, or `a, c or i text not ended by a . line`, logged as an
+`err`): sam takes the end of input for a `.`, but a block that reaches Edit
+unfinished was cut short. A fragment never runs on its
own. A line or Edit block held past 1 MiB is refused (EINVAL).
A look takes acme's addresses after a colon (editors/acme/look.c:450): a
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 398824d5..54d21da6 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1536,3 +1536,14 @@ test "an Edit block still open when its open closes fails there with an err and
try testing.expect(th.logHas(p, "unmatched"));
try testing.expectEqualStrings("abc\n", p.panes[0].?.file.?.content);
}
+
+test "an a, c or i text still open when its open closes fails there too and changes nothing" {
+ const p = try th.withFile(testing.allocator, "abc\n");
+ defer p.deinit();
+ const ctl_node = Node.of(serialOf(p), .ctl);
+ const c = call(p, .{ .tag = 1, .op = .open, .node = ctl_node, .omode = 1 }).reply.handle;
+ _ = call(p, .{ .tag = 2, .op = .write, .node = ctl_node, .handle = c, .data = "Edit $a\nhalf of a block\n" });
+ _ = call(p, .{ .tag = 3, .op = .release, .node = ctl_node, .handle = c, .opened = true });
+ try testing.expect(th.logHas(p, "not ended by a . line"));
+ try testing.expectEqualStrings("abc\n", p.panes[0].?.file.?.content);
+}
diff --git a/src/sam_edit.zig b/src/sam_edit.zig
index 604e2b31..cd2998ef 100644
--- a/src/sam_edit.zig
+++ b/src/sam_edit.zig
@@ -75,6 +75,11 @@ pub fn run(arena: std.mem.Allocator, text: []const u8, dot: Range, name: []const
if (last) |l| l.next = c else first = c;
last = c;
}
+ // An a, c or i text that never met its `.` line is refused, as an
+ // unclosed `{` is: sam takes the end of input for one, but a block that
+ // reaches Edit unfinished was cut short (a write, an open closed), and
+ // half of it inserted is worse than none.
+ if (ps.open_text) return fail(why, "a, c or i text not ended by a . line", .{});
const cmds = first orelse return .{ .ops = &.{}, .dot = dot, .printed = "" };
// acme's u runs at once (ecmd.c:532), outside the edit log; here it is
// an Edit of its own.