summaryrefslogtreecommitdiff
path: root/src/normal.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/normal.zig')
-rw-r--r--src/normal.zig53
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));
+ }
+ }
+}