From ff867666106fa85bd91341beb9e8121b2f99bc5f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 18:58:24 -0300 Subject: ^ in a pattern with \n matches at every line start, and a ^ or $ that cannot work is refused A pattern naming a newline runs over the whole text, where mvzr's ^ is only the text's start: /^def .*\n/ from #0 failed, and Edit ,x/^def .*\n/.../ silently did nothing. A leading ^ is now tried at each line start from the search's own; $ just before \n is dropped, as it changes nothing; any other ^ or $ in such a pattern is refused with why, since mvzr would read it as the text's ends. ^ inside an alternation holds only where a search starts, an mvzr limit, documented. Co-Authored-By: Claude Opus 5.5 --- src/sam_edit.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/sam_edit.zig') diff --git a/src/sam_edit.zig b/src/sam_edit.zig index c08c6cdd..40221628 100644 --- a/src/sam_edit.zig +++ b/src/sam_edit.zig @@ -380,7 +380,10 @@ const Exec = struct { } fn compile(ex: *Exec, pat: []const u8, c: u8) Failure!regexp.Regex { - return regexp.Regex.compile(pat) catch fail(ex.why, "bad regexp in {c} command", .{c}); + return regexp.Regex.compile(pat) catch |err| switch (err) { + error.Anchor => fail(ex.why, "{s}", .{regexp.Regex.e_anchor}), + error.Bad => fail(ex.why, "bad regexp in {c} command", .{c}), + }; } fn find(ex: *Exec, rx: *regexp.Regex, from: usize, hi: usize) Failure!?regexp.Regex.Match { @@ -637,6 +640,8 @@ test "sam's classic commands" { try expectEdit("one\ntwo\n", "1 m $", "two\none\n"); try expectEdit("one\ntwo\n", "1 t $", "one\ntwo\none\n"); try expectEdit("one\ntwo\nthree\n", "2 d", "one\nthree\n"); + // ^ at every line start in a pattern that spans lines (dogfood round 5). + try expectEdit("x = 1\ndef a\ndef b\n", ",x/^def .*\\n/a/X\\n/", "x = 1\ndef a\nX\ndef b\nX\n"); // pardes's line:col. try expectEdit("one\ntwo\n", "2:2 i/-/", "one\nt-wo\n"); } @@ -653,6 +658,7 @@ test "an Edit that fails halfway changes nothing, and says why in acme's words" .{ "w /tmp/x", "w is not supported in pardes" }, .{ ",s/(a)/\\1/", "no \\1: mvzr keeps no submatches" }, .{ "1 m 1,2", "move overlaps itself" }, + .{ ",x/(^|\\n)foo/d", "in a pattern with \\n, ^ can only come first and $ only just before a \\n" }, }) |c| { var why: Why = .{}; try std.testing.expectError(error.Edit, run(arena_state.allocator(), "foo a\nfoo\n", .{}, "t", c[0], &why)); -- cgit v1.3