diff options
Diffstat (limited to 'src/normal.zig')
| -rw-r--r-- | src/normal.zig | 53 |
1 files changed, 42 insertions, 11 deletions
diff --git a/src/normal.zig b/src/normal.zig index ffbb49d5..8c5f232d 100644 --- a/src/normal.zig +++ b/src/normal.zig @@ -6,7 +6,7 @@ const tagline = @import("tagline.zig"); const exec = @import("exec.zig"); const look = @import("look.zig"); const std = @import("std"); -const mvzr = @import("mvzr"); +const regexp = @import("regexp.zig"); const modal = @import("modal.zig"); const panes = @import("panes.zig"); const edit = @import("edit.zig"); @@ -92,7 +92,7 @@ pub fn applySelRegex(p: *Pardes, pane: *Pane, t: *Text, pat: []const u8, split: const snap = pane.sel_snap[0..pane.nsel_snap]; var out: [Text.max_selections]modal.Selection = undefined; var m: usize = 0; - if (pat.len > 0) if (mvzr.compile(pat)) |re| { + if (regexp.Regex.compile(pat)) |re| { var hay_all = text; if (for (pat) |c| { if (std.ascii.isUpper(c)) break false; @@ -105,17 +105,18 @@ pub fn applySelRegex(p: *Pardes, pane: *Pane, t: *Text, pat: []const u8, split: const from = @min(r.anchor, r.head); const to = @min(@max(r.anchor, r.head), text.len); if (from >= to) continue; - const hay = hay_all[from..to]; - var at: usize = 0; + // Searched as sam searches, the way addr does (regexp.zig): the + // text around the selection still says where its lines begin. + var at = from; var piece = from; // split: where the next piece begins - while (at < hay.len and m < Text.max_selections) { - const hit_at = re.matchPos(at, hay) orelse break; + while (at < to and m < Text.max_selections) { + const hit_at = re.find(hay_all, at, to, to) orelse break; if (split) { - out[m] = .{ .anchor = piece, .head = from + hit_at.start }; + out[m] = .{ .anchor = piece, .head = hit_at.start }; m += 1; - piece = from + hit_at.end; - } else if (from + hit_at.start != to) { - out[m] = .{ .anchor = from + hit_at.start, .head = from + hit_at.end }; + piece = hit_at.end; + } else if (hit_at.start != to) { + out[m] = .{ .anchor = hit_at.start, .head = hit_at.end }; m += 1; } // an empty match would otherwise never advance @@ -126,7 +127,7 @@ pub fn applySelRegex(p: *Pardes, pane: *Pane, t: *Text, pat: []const u8, split: m += 1; } } - }; + } if (m == 0) { // the text CAN move under an armed prompt (a tag chord runs a // builtin), and these are raw offsets into the surface as it was @@ -730,3 +731,33 @@ test "flat text movement and selection replay need no scratch rows" { executeNormalAction(p, &pane.body, .match_bracket); try std.testing.expectEqual(@as(i32, 8), pane.body.cur_col); } + +test "s and S search a multi-line selection as addr does: ^ and $ per line, . within one" { + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 60, .rows = 12 }); + defer p.deinit(); + const text = "alpha x\nbeta alpha\nalphabet\n"; + const pane = try p.setTestFile(text); + const Case = struct { pat: []const u8, split: bool, want: []const [2]usize }; + for ([_]Case{ + // ^ at each line's start, not only the selection's + .{ .pat = "^alpha", .split = false, .want = &.{ .{ 0, 5 }, .{ 19, 24 } } }, + // $ at each line's end + .{ .pat = "alpha$", .split = false, .want = &.{.{ 13, 18 }} }, + // . stops at the newline: one selection per line, not one for all + .{ .pat = "a.*", .split = false, .want = &.{ .{ 0, 7 }, .{ 11, 18 }, .{ 19, 27 } } }, + // S splits on a line's end + .{ .pat = "$", .split = true, .want = &.{ .{ 0, 7 }, .{ 7, 18 }, .{ 18, 27 }, .{ 27, 28 } } }, + }) |c| { + pane.sel_snap[0] = .{ .anchor = 0, .head = text.len }; + pane.nsel_snap = 1; + applySelRegex(p, pane, &pane.body, c.pat, c.split); + var out: [Text.max_selections]modal.Selection = undefined; + const got = pane.body.ranges(text, 0, &out); + try std.testing.expectEqual(c.want.len, got.n); + for (c.want, out[0..got.n]) |w, r| { + try std.testing.expectEqual(w[0], @min(r.anchor, r.head)); + try std.testing.expectEqual(w[1], @max(r.anchor, r.head)); + } + } +} |
