summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 16:29:20 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit8496759196e88e4fbd25ce19d3b04722c8f25f64 (patch)
tree449acacc2ac676c466987354e34bcf9c6e5616f1 /src
parent77c535a4f5acb99e55ad8312063f65cc85a3bcd9 (diff)
downloadpardes-8496759196e88e4fbd25ce19d3b04722c8f25f64.tar.gz
pardes-8496759196e88e4fbd25ce19d3b04722c8f25f64.zip
A bad command inside an Edit block written whole is named, not the } after it
A command in a { block that failed to parse ended the block there, so its } was taken as a control message of its own and the write failed unknown control message "}". A { block not yet closed now runs to its } whatever fails to parse in it, and the error names the command (Edit: unknown command z), as the line-by-line path does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/ninep/ctl.zig10
-rw-r--r--src/sam_edit.zig17
2 files changed, 26 insertions, 1 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 63e7e76a..9d7557e6 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -3030,3 +3030,13 @@ test "@p<terminal>:<address> takes any address over the terminal's lines, select
try testing.expectEqual(row_before, pane.body.cur_row);
try testing.expectEqualStrings("", rd(p, root, 0, 64).bytes);
}
+
+test "a bad command inside an Edit block written whole is named, not the } after it" {
+ const p = try withFile(testing.allocator, "abc\n");
+ defer p.deinit();
+ const r = wr(p, Node.of(serialOf(p), .ctl), "Edit {\n,s/a/b/\nzq\n}\n");
+ try testing.expectEqual(Status.err, r.reply.status);
+ try testing.expect(std.mem.indexOf(u8, r.reply.ename, "\"}\"") == null);
+ try testing.expect(std.mem.indexOf(u8, r.reply.ename, "z") != null);
+ try testing.expectEqualStrings("abc\n", p.panes[0].?.file.?.content);
+}
diff --git a/src/sam_edit.zig b/src/sam_edit.zig
index 8f7c961f..ea1105bd 100644
--- a/src/sam_edit.zig
+++ b/src/sam_edit.zig
@@ -111,11 +111,26 @@ pub fn waitsFor(arena: std.mem.Allocator, command: []const u8) Wait {
var ps: Parser = .{ .arena = arena, .src = src, .why = &why };
while (true) {
const c = ps.parse(0) catch
- return if (ps.open_text) .text else if (std.mem.eql(u8, why.text(), "unmatched `{'")) .group else .none;
+ return if (ps.open_text) .text else if (std.mem.eql(u8, why.text(), "unmatched `{'") or groupOpen(command)) .group else .none;
if (c == null) return if (ps.open_text) .text else .none;
}
}
+/// A `{` block not closed yet by its `}` line: a command in it that fails
+/// to parse still leaves the block to end at its `}`, so the error is that
+/// command's, not the `}`'s taken as a line of its own.
+fn groupOpen(command: []const u8) bool {
+ var depth: usize = 0;
+ var lines = std.mem.splitScalar(u8, command, '\n');
+ while (lines.next()) |raw| {
+ const line = std.mem.trim(u8, raw, " \t\r");
+ if (std.mem.eql(u8, line, "}")) {
+ depth -|= 1;
+ } else if (std.mem.endsWith(u8, line, "{")) depth += 1;
+ }
+ return depth > 0;
+}
+
pub fn needsMore(arena: std.mem.Allocator, command: []const u8) bool {
var why: Why = .{};
const src = std.fmt.allocPrint(arena, "{s}\n", .{command}) catch return false;