summaryrefslogtreecommitdiff
path: root/src/sam_edit.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 15:59:31 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit1e5904677535d219e9bba93964e531c786870539 (patch)
tree54018fc4e106be91085dc1f7e93dc69cb7bf41ed /src/sam_edit.zig
parentfa07370ea6cbd4be031bab2c14b24203ae329365 (diff)
downloadpardes-1e5904677535d219e9bba93964e531c786870539.tar.gz
pardes-1e5904677535d219e9bba93964e531c786870539.zip
An Edit x gives each of its searches the step budget, and a search that spends it says it took too much time
The budget was the compiled pattern's, spent over every search an x made: Edit ,x/x+/c/z/ over 100k lines ran out and failed, blamed on backtracking. Each search now starts with the whole budget, and one that spends it says regular expression search took too much time, gave up. (Not "too long": 9ns reads those words as ENAMETOOLONG.) Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/sam_edit.zig')
-rw-r--r--src/sam_edit.zig17
1 files changed, 17 insertions, 0 deletions
diff --git a/src/sam_edit.zig b/src/sam_edit.zig
index 113ad14f..8f7c961f 100644
--- a/src/sam_edit.zig
+++ b/src/sam_edit.zig
@@ -456,6 +456,9 @@ const Exec = struct {
/// reaches back into what the last one took.
fn find(ex: *Exec, rx: *regexp.Regex, from: usize, hi: usize) Failure!?regexp.Regex.Match {
if (from > hi) return null;
+ // The budget is each search's own: an x over 100k lines makes 100k
+ // searches, none of which is the slow one.
+ rx.steps = regexp.Regex.budget;
const m = (rx.find(ex.text, from, hi, hi) catch return fail(ex.why, "{s}", .{addr_lang.e_slow})) orelse return null;
var start = modal.runeStart(ex.text, m.start);
if (start < from) start = modal.runeEnd(ex.text, m.start);
@@ -694,6 +697,20 @@ fn expectEdit(text: []const u8, command: []const u8, want: []const u8) !void {
try std.testing.expectEqualStrings(want, got);
}
+test "an x over 100k lines is 100k searches, each with its own step budget" {
+ const th = @import("ninep/testing.zig");
+ const line = "x" ** 80 ++ "\n";
+ const text = try std.testing.allocator.alloc(u8, line.len * 100_000);
+ defer std.testing.allocator.free(text);
+ for (0..100_000) |k| @memcpy(text[line.len * k ..][0..line.len], line);
+ const p = try th.withFile(std.testing.allocator, text);
+ defer p.deinit();
+ const tree = @import("ninep/tree.zig");
+ const r = th.wr(p, tree.Node.of(th.serialOf(p), .ctl), "Edit ,x/x+/c/z/\n");
+ try std.testing.expectEqual(tree.Status.ok, r.reply.status);
+ try std.testing.expect(std.mem.startsWith(u8, p.panes[0].?.file.?.content, "z\nz\n"));
+}
+
test "sam's classic commands" {
try expectEdit("foo x foo y foo\n", ",x/foo/c/foobar/", "foobar x foobar y foobar\n");
try expectEdit("a b\nc d\n", ",x/ /c/_/", "a_b\nc_d\n");