From e2452380ed42d3b418930e375780745612d599c5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 04:56:17 -0300 Subject: 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 --- src/ninep/tree.zig | 11 +++++++++++ src/sam_edit.zig | 5 +++++ 2 files changed, 16 insertions(+) (limited to 'src') 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. -- cgit v1.3