summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ninep/addr.zig2
-rw-r--r--src/sam_edit.zig17
2 files changed, 18 insertions, 1 deletions
diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig
index c052a665..005ad593 100644
--- a/src/ninep/addr.zig
+++ b/src/ninep/addr.zig
@@ -17,7 +17,7 @@ pub const e_col_zero = "address out of range: a column counts from 1";
pub const e_regexp = "bad regular expression";
/// Not "took too long": 9ns reads errors by their words, and that would be
/// ENAMETOOLONG.
-pub const e_slow = "regular expression search gave up, backtracking past its step budget";
+pub const e_slow = "regular expression search took too much time, gave up";
pub const e_syntax = "bad address syntax";
pub const e_order = "addresses out of order";
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");