summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-31 11:42:18 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:08 -0300
commit911215846f1d5a9b2e37a00e74fb9266d4a0884a (patch)
tree31413ca3336cbbd1933d9dc3e24f09a3b8ef46b4
parent0b7a480bef38b209741c520e2478d178767a9e51 (diff)
downloadpardes-911215846f1d5a9b2e37a00e74fb9266d4a0884a.tar.gz
pardes-911215846f1d5a9b2e37a00e74fb9266d4a0884a.zip
a Look path can name a range, and search selects what it found
file:LINE:COL-ENDLINE:ENDCOL, with the two short forms people actually type reading naturally: file:412:9-21 on one line, file:412-418 whole ones. Ends are inclusive. A path feature, not a search feature — a ranged path typed in a tag or middle-clicked out of a shell's output selects just the same; search is only its first consumer. The dash is the fussy part. `-` was already a file char, so a ranged word survives click expansion whole, but a range needs a number on BOTH sides or my-file:10, build-2 and 2026-07-30 would stop being paths. Table-driven test in look.zig for exactly that. Selecting goes through the cellRange/setPaneRange pair the multi-cursor work left, and hxOff clamps both ends, so a stale range selects what still exists rather than crashing or reaching past EOF — pinned with an 8:6-400:9 range in a nine-line file. Producers: / search, Grep, and five LSP sites through a new spanRow — goto, references, rename tokens and both symbol lists were throwing away real protocol ranges at path:line:col. Left alone deliberately: Find rows are bare paths with nothing to span, a jump is a spot not a span, and the diagnostic and format paths only ever have a point, where half a range would be worse than none. One knock-on worth knowing: n now leaves an EXPLICIT selection, so a topbar execute chords it. grep.snap's no-match step was silently becoming `Grep TARGET`; it runs from the leader path now, which never chords, and the dedicated chord steps stayed where they were.
-rw-r--r--docs/lsp.md11
-rw-r--r--src/builtins.zig2
-rw-r--r--src/config.zig21
-rw-r--r--src/look.zig144
-rw-r--r--src/lsp/lsp.zig36
-rw-r--r--src/lsp/lsp_zls.zig10
-rw-r--r--src/main.zig2
-rw-r--r--src/pardes.zig93
-rw-r--r--src/tutor.txt15
-rw-r--r--test/snapshots/fsearch.golden58
-rw-r--r--test/snapshots/fsearch.snap15
-rw-r--r--test/snapshots/grep.golden58
-rw-r--r--test/snapshots/grep.snap19
-rw-r--r--test/snapshots/lookrange.golden496
-rw-r--r--test/snapshots/lookrange.snap70
-rw-r--r--test/snapshots/lsp.golden26
-rw-r--r--test/snapshots/lsp.snap3
-rw-r--r--test/snapshots/lspdebug.golden2
-rw-r--r--test/snapshots/psearch.golden64
-rw-r--r--test/snapshots/psearch.snap12
-rw-r--r--test/snapshots/rsearch.golden16
-rw-r--r--test/snapshots/rsearch.snap4
22 files changed, 975 insertions, 202 deletions
diff --git a/docs/lsp.md b/docs/lsp.md
index a22a4c52..c89c42f7 100644
--- a/docs/lsp.md
+++ b/docs/lsp.md
@@ -51,11 +51,16 @@ Three rules make it safe:
Every backend renders into one format:
```
-/abs/path/to/file.zig:LINE:COL text
+/abs/path/to/file.zig:LINE:COL text
+/abs/path/to/file.zig:LINE:COL-ENDCOL text
```
-1-based line and column, absolute path. This is the format `look.zig` already
-resolves, `runSearch` already produces and `n`/`N` already step — so:
+1-based line and column, absolute path. The second form carries the answer's
+RANGE where the protocol gave one on a single line (a token, a symbol's name),
+and a look on it SELECTS that span rather than parking at its first cell — so
+`gd` lands on the whole name and `gr` steps references with each one
+highlighted. This is the format `look.zig` already resolves, `runSearch`
+already produces and `n`/`N` already step — so:
- **one row from a goto** → jump straight there (`lookAt`)
- **several rows** → an output buffer, which `n`/`N` walk
diff --git a/src/builtins.zig b/src/builtins.zig
index 6593be7d..7378e395 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -460,7 +460,7 @@ pub const Last = struct {
while (i > 0) {
i -= 1;
const j = c.p.jumps[i];
- if (j.pane != c.id) return c.p.focusPaneLine(j.pane, j.line, j.col);
+ if (j.pane != c.id) return c.p.focusPaneLine(j.pane, .{ .line = j.line, .col = j.col });
}
}
};
diff --git a/src/config.zig b/src/config.zig
index 8f2175f6..2603a40c 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -430,6 +430,27 @@ pub fn wordBounds(line: []const u8, col: usize) struct { lo: usize, hi: usize }
/// place.
pub const line_col_sep: u8 = ':';
+/// ...and separates that spot from the END of a RANGE. A look at a ranged path
+/// SELECTS the span rather than just parking on its first cell, which is what
+/// lets a search result carry the text it matched and `n` land ON it.
+///
+/// Three spellings. The long one subsumes the other two, but the short ones
+/// are what a person actually types and what a grep-alike emits, so all three
+/// parse:
+/// main.zig:412-418 lines 412 through 418, whole
+/// main.zig:412:9-21 line 412, columns 9 through 21
+/// main.zig:412:9-418:1 line 412 column 9 through line 418 column 1
+/// Both ends are INCLUSIVE and 1-based, like the spot they extend — `412-418`
+/// reads as seven lines, not six. `main.zig:412` and `main.zig:412:9` keep
+/// meaning exactly what they always did.
+///
+/// Must be an isFileChar member, same as the separator above, or a click would
+/// expand to half a range. That is also why reading it is FUSSY (look.zig,
+/// parsePathLine): ordinary paths are full of dashes, so the suffix counts as
+/// a range only when a NUMBER follows the dash — `my-file:10` and `build-2`
+/// stay the paths they are.
+pub const range_sep: u8 = '-';
+
/// `@p7:10:5` — pane 7, line 10, column 5. The one look target that names a
/// live pane instead of a path, because terminals and output buffers have no
/// file for a location to point at. Both the writer (a `/` result row) and the
diff --git a/src/look.zig b/src/look.zig
index c4d04576..9df2b485 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -55,41 +55,125 @@ pub fn openLink(url: []const u8) void {
_ = libc.waitpid(pid, null, 0);
}
-/// peel a trailing :LINE[:COL] suffix (both 1-based, 0 = absent):
-/// main.zig:100 -> {main.zig, 100, 0}
-/// main.zig:100:7 -> {main.zig, 100, 7}
-/// main.zig:100: -> {main.zig, 100, 0} grep -n's trailing delimiter
-pub fn parsePathLine(tok: []const u8) struct { path: []const u8, line: usize, col: usize } {
+/// WHERE in a pane a look word points. A spot (`:LINE:COL`) — or a SPAN, when
+/// the word carries a range (config.range_sep), which a look SELECTS instead
+/// of merely parking on. Everything is 1-based and 0 means absent, so a bare
+/// path is the all-zero Spot and `end_line == 0` is the question "is this a
+/// range".
+pub const Spot = struct {
+ line: usize = 0,
+ col: usize = 0,
+ end_line: usize = 0,
+ /// 0 with a live `end_line` is the whole-lines form: through the END of
+ /// end_line, newline included, which is what helix's `x` selects.
+ end_col: usize = 0,
+};
+
+/// digits at `i` and where they end; `end == i` means there were none. Four
+/// numbers now come out of the same token, and spelling the scan four times
+/// is how one of them ends up subtly different from the others.
+fn num(tok: []const u8, i: usize) struct { v: usize, end: usize } {
+ var v: usize = 0;
+ var j = i;
+ while (j < tok.len and std.ascii.isDigit(tok[j])) : (j += 1) v = v * 10 + (tok[j] - '0');
+ return .{ .v = v, .end = j };
+}
+
+/// peel a trailing :LINE[:COL] spot, or one of the three range spellings, off
+/// a look word (config.line_col_sep / config.range_sep own both characters):
+/// main.zig:100 -> line 100
+/// main.zig:100:7 -> line 100, col 7
+/// main.zig:100: -> line 100 grep -n's trailing delimiter
+/// main.zig:100-104 -> lines 100..104 whole
+/// main.zig:100:7-21 -> line 100, cols 7..21
+/// main.zig:100:7-104:3 -> line 100 col 7 .. line 104 col 3
+///
+/// A tail that does not parse leaves the token a plain PATH, which is the rule
+/// that keeps the dash safe: `a-b`, `build-2:3` and `x:1-y` are all paths (the
+/// last one goes back to hunting for a later ':' and finds none), because a
+/// range needs a number on both sides of its dash.
+pub fn parsePathLine(tok: []const u8) struct { path: []const u8, at: Spot } {
var sep: usize = 0;
while (sep < tok.len) : (sep += 1) {
if (tok[sep] != config.line_col_sep) continue;
- var j = sep + 1;
- var line: usize = 0;
- while (j < tok.len and std.ascii.isDigit(tok[j])) : (j += 1) line = line * 10 + (tok[j] - '0');
- if (j == sep + 1) continue; // no digits after ':'
- if (j < tok.len and tok[j] != config.line_col_sep) continue; // junk after the number
- var col: usize = 0;
- if (j < tok.len) {
- var k = j + 1;
- while (k < tok.len and std.ascii.isDigit(tok[k])) : (k += 1) col = col * 10 + (tok[k] - '0');
- // digits, and nothing but a delimiter after them, or no column
- if (k == j + 1 or (k < tok.len and tok[k] != config.line_col_sep)) col = 0;
+ const l = num(tok, sep + 1);
+ if (l.end == sep + 1) continue; // no digits after ':'
+ const path = tok[0..sep];
+ var i = l.end;
+ // `:LINE-ENDLINE`: whole lines, no column anywhere in the form
+ if (i < tok.len and tok[i] == config.range_sep) {
+ const e = num(tok, i + 1);
+ if (e.end == i + 1) continue; // a dash with no number is not a range
+ if (e.end < tok.len and tok[e.end] != config.line_col_sep) continue; // junk after it
+ return .{ .path = path, .at = .{ .line = l.v, .end_line = e.v } };
}
- return .{ .path = tok[0..sep], .line = line, .col = col };
+ if (i < tok.len and tok[i] != config.line_col_sep) continue; // junk after the number
+ var at: Spot = .{ .line = l.v };
+ if (i == tok.len) return .{ .path = path, .at = at };
+ // `:COL`. A column that does not parse is dropped and the LINE still
+ // stands, which is how this has always read a half-mangled suffix.
+ const c = num(tok, i + 1);
+ if (c.end == i + 1) return .{ .path = path, .at = at };
+ if (c.end < tok.len and tok[c.end] != config.line_col_sep and tok[c.end] != config.range_sep)
+ return .{ .path = path, .at = at };
+ at.col = c.v;
+ i = c.end;
+ if (i == tok.len or tok[i] != config.range_sep) return .{ .path = path, .at = at };
+ // `-ENDCOL` on this same line, unless a `:ENDCOL` follows — then that
+ // first number was the end LINE all along. One lookahead, and it is
+ // what lets the two-number and four-number forms share a spelling.
+ const e = num(tok, i + 1);
+ if (e.end == i + 1) return .{ .path = path, .at = at };
+ at.end_line = at.line;
+ at.end_col = e.v;
+ if (e.end < tok.len and tok[e.end] == config.line_col_sep) {
+ const e2 = num(tok, e.end + 1);
+ if (e2.end > e.end + 1) {
+ at.end_line = at.end_col;
+ at.end_col = e2.v;
+ }
+ }
+ return .{ .path = path, .at = at };
+ }
+ return .{ .path = tok, .at = .{} };
+}
+
+test "parsePathLine: spots, ranges, and the paths that merely look like them" {
+ const cases = [_]struct { tok: []const u8, path: []const u8, at: Spot }{
+ .{ .tok = "main.zig", .path = "main.zig", .at = .{} },
+ .{ .tok = "main.zig:100", .path = "main.zig", .at = .{ .line = 100 } },
+ .{ .tok = "main.zig:100:", .path = "main.zig", .at = .{ .line = 100 } },
+ .{ .tok = "main.zig:100:7", .path = "main.zig", .at = .{ .line = 100, .col = 7 } },
+ .{ .tok = "main.zig:100-104", .path = "main.zig", .at = .{ .line = 100, .end_line = 104 } },
+ .{ .tok = "main.zig:100:7-21", .path = "main.zig", .at = .{ .line = 100, .col = 7, .end_line = 100, .end_col = 21 } },
+ .{ .tok = "main.zig:100:7-104:3", .path = "main.zig", .at = .{ .line = 100, .col = 7, .end_line = 104, .end_col = 3 } },
+ // the dash cases that must stay ORDINARY PATHS
+ .{ .tok = "my-file.zig", .path = "my-file.zig", .at = .{} },
+ .{ .tok = "my-file:10", .path = "my-file", .at = .{ .line = 10 } },
+ .{ .tok = "x:1-y", .path = "x:1-y", .at = .{} },
+ .{ .tok = "a-b-c", .path = "a-b-c", .at = .{} },
+ .{ .tok = "2026-07-30", .path = "2026-07-30", .at = .{} },
+ // a mangled tail still yields what parsed (unchanged behaviour)
+ .{ .tok = "main.zig:100x", .path = "main.zig:100x", .at = .{} },
+ .{ .tok = "main.zig:100:7x", .path = "main.zig", .at = .{ .line = 100 } },
+ };
+ for (cases) |c| {
+ const got = parsePathLine(c.tok);
+ try std.testing.expectEqualStrings(c.path, got.path);
+ try std.testing.expectEqual(c.at, got.at);
}
- return .{ .path = tok, .line = 0, .col = 0 };
}
pub const Target = union(enum) {
none,
dir: []const u8, // resolved absolute path, in caller's buf
- file: struct { path: []const u8, line: usize, col: usize },
+ file: struct { path: []const u8, at: Spot },
image: struct { path: []const u8 },
url: []const u8,
/// `@p7:10:5` — pane 7, line 10, column 5 (0 = unspecified). The one
/// target that names a live pane instead of a path, because terminals and
/// output buffers have no file for a location to point at.
- pane: struct { id: usize, line: usize, col: usize },
+ pane: struct { id: usize, at: Spot },
};
pub fn isImagePath(path: []const u8) bool {
@@ -114,7 +198,7 @@ pub fn resolve(word_raw: []const u8, cwd: []const u8, realbuf: *[4096]u8) Target
for (word[config.pane_addr.len..]) |c| {
if (!std.ascii.isDigit(c)) break;
id = id * 10 + (c - '0');
- } else return .{ .pane = .{ .id = id, .line = pl.line, .col = pl.col } };
+ } else return .{ .pane = .{ .id = id, .at = pl.at } };
}
// a URL is a URL everywhere: no filesystem can answer it, so it leaves the
@@ -134,12 +218,12 @@ pub fn resolve(word_raw: []const u8, cwd: []const u8, realbuf: *[4096]u8) Target
const resolved = std.mem.span(rp);
if (isDir(rp)) return .{ .dir = resolved };
if (isImagePath(resolved)) return .{ .image = .{ .path = resolved } };
- return .{ .file = .{ .path = resolved, .line = pl.line, .col = pl.col } };
+ return .{ .file = .{ .path = resolved, .at = pl.at } };
} else {
// web: tracked Zig sources resolve inside the build-generated,
// read-only source filesystem.
if (resolveEmbedded(word, cwd, realbuf)) |source|
- return .{ .file = .{ .path = source.path, .line = pl.line, .col = pl.col } };
+ return .{ .file = .{ .path = source.path, .at = pl.at } };
return .none;
}
}
@@ -280,10 +364,12 @@ pub fn find(arena: std.mem.Allocator, dir: []const u8, pat: []const u8, out: *st
const grep_max_bytes = 256 * 1024;
const grep_max_files = 20_000;
-/// every line of `text` holding `pat`, as `path:LINE:COL text` rows — the
-/// shared half of grep(), and the shape every result row in pardes has: the
-/// leading word is a look target, so n/N walk the hits. Returns the rows
-/// written, at most `budget`.
+/// every line of `text` holding `pat`, as `path:LINE:COL-ENDCOL text` rows —
+/// the shared half of grep(), and the shape every result row in pardes has:
+/// the leading word is a look target, so n/N walk the hits. The row names the
+/// MATCH's span and not just its first cell, so stepping onto one selects the
+/// text that matched (config.range_sep). Returns the rows written, at most
+/// `budget`.
fn grepText(arena: std.mem.Allocator, path: []const u8, text: []const u8, pat: []const u8, out: *std.ArrayList(u8), budget: usize) usize {
var n: usize = 0;
var line: usize = 0;
@@ -298,7 +384,9 @@ fn grepText(arena: std.mem.Allocator, path: []const u8, text: []const u8, pat: [
const ln = std.mem.trimEnd(u8, raw, " \t\r");
var cut = @min(ln.len, 200);
while (cut > 0 and cut < ln.len and ln[cut] & 0xc0 == 0x80) cut -= 1;
- const row = std.fmt.allocPrint(arena, "{s}:{d}:{d} {s}\n", .{ path, line, at + 1, ln[0..cut] }) catch break;
+ const row = std.fmt.allocPrint(arena, "{s}:{d}:{d}{c}{d} {s}\n", .{
+ path, line, at + 1, config.range_sep, at + pat.len, ln[0..cut],
+ }) catch break;
out.appendSlice(arena, row) catch break;
n += 1;
}
diff --git a/src/lsp/lsp.zig b/src/lsp/lsp.zig
index abb91874..b8356724 100644
--- a/src/lsp/lsp.zig
+++ b/src/lsp/lsp.zig
@@ -7,10 +7,12 @@
//! language query is just another thing that answers later.
//!
//! Every backend renders into ONE format: `+Search` rows. A location is
-//! `path:LINE:COL text`, which is what look.zig already resolves and what n/N
-//! already steps, so a multi-result answer IS helix's picker and a single
-//! result IS a jump — with no picker UI written for it. Free text (hover,
-//! a rename's diff) rides the same buffer as plain lines.
+//! `path:LINE:COL text` — or `path:LINE:COL-ENDCOL text` where the protocol
+//! answered with a real range, which a look then SELECTS — and that is what
+//! look.zig already resolves and what n/N already steps, so a multi-result
+//! answer IS helix's picker and a single result IS a jump, with no picker UI
+//! written for it. Free text (hover, a rename's diff) rides the same buffer as
+//! plain lines.
//!
//! `query` is the ONLY thing an implementation supplies. Swapping backends is
//! swapping this one function, which is also how the three competing
@@ -101,6 +103,32 @@ pub fn row(
}) catch {};
}
+/// The same row for a protocol RANGE: `path:LINE:COL-ENDCOL`, which a look
+/// SELECTS rather than parking on its first cell — so `gd` lands on the whole
+/// name and a references list steps symbol by symbol with each one highlighted
+/// (config.range_sep spells the dash; `-` is written out here for the same
+/// reason `:` is).
+///
+/// `end_col` is the protocol's own EXCLUSIVE end character, which is already
+/// the 1-based inclusive column pardes wants, so the conversion is the absence
+/// of one. A span that is empty or crosses lines falls back to the point row:
+/// the only multi-line ranges here are whole declarations, and a goto onto one
+/// wants the cursor at its name, not its body painted.
+pub fn spanRow(
+ out: *std.Io.Writer,
+ path: []const u8,
+ line: usize,
+ col: usize,
+ end_line: usize,
+ end_col: usize,
+ text: []const u8,
+) void {
+ if (end_line != line or end_col <= col) return row(out, path, line, col, text);
+ out.print("{s}:{d}:{d}-{d} {s}\n", .{
+ path, line + 1, col + 1, end_col, std.mem.trim(u8, text, " \t\r\n"),
+ }) catch {};
+}
+
/// Byte offset -> (line, column), both 0-based. Every backend needs it to turn
/// an AST token into a row, so it lives here rather than three times over.
pub fn lineCol(source: []const u8, offset: usize) struct { line: usize, col: usize } {
diff --git a/src/lsp/lsp_zls.zig b/src/lsp/lsp_zls.zig
index 8a36a151..6090af1a 100644
--- a/src/lsp/lsp_zls.zig
+++ b/src/lsp/lsp_zls.zig
@@ -484,7 +484,7 @@ fn rowForToken(arena: std.mem.Allocator, th: Analyser.TokenWithHandle, out: *std
if (th.token >= tree.tokens.len) return;
const r = offsets.tokenToRange(tree, th.token, enc);
const path = th.handle.uri.toFsPath(arena) catch return;
- lsp.row(out, path, r.start.line, r.start.character, lineAt(tree.source, r.start.line));
+ lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, lineAt(tree.source, r.start.line));
}
// ---------------------------------------------------------------- goto
@@ -632,7 +632,7 @@ fn goto(
const t2 = &nd.handle.tree;
const rr = offsets.nodeToRange(t2, nd.node, enc);
const path = nd.handle.uri.toFsPath(arena) catch continue;
- lsp.row(out, path, rr.start.line, rr.start.character, lineAt(t2.source, rr.start.line));
+ lsp.spanRow(out, path, rr.start.line, rr.start.character, rr.end.line, rr.end.character, lineAt(t2.source, rr.start.line));
},
}
}
@@ -703,7 +703,7 @@ fn emitSymbols(
try std.fmt.allocPrint(arena, "{s} {s}", .{ name, d })
else
name;
- lsp.row(out, path, s.selectionRange.start.line, s.selectionRange.start.character, text);
+ lsp.spanRow(out, path, s.selectionRange.start.line, s.selectionRange.start.character, s.selectionRange.end.line, s.selectionRange.end.character, text);
if (s.children) |kids| try emitSymbols(gpa, arena, path, kids, name, n, out);
}
}
@@ -752,7 +752,7 @@ fn filterSymbols(
try std.fmt.allocPrint(arena, "{s}.{s}", .{ prefix, s.name });
if (containsIgnoreCase(s.name, needle)) {
n.* += 1;
- lsp.row(out, path, s.selectionRange.start.line, s.selectionRange.start.character, name);
+ lsp.spanRow(out, path, s.selectionRange.start.line, s.selectionRange.start.character, s.selectionRange.end.line, s.selectionRange.end.character, name);
}
if (s.children) |kids| try filterSymbols(gpa, arena, path, kids, name, needle, n, out);
}
@@ -824,7 +824,7 @@ fn references(
try std.fmt.allocPrint(arena, "{s} -> {s} {s}", .{ want, nn, std.mem.trim(u8, lines.line(r.start.line), " \t") })
else
lines.line(r.start.line);
- lsp.row(out, handle.uri.toFsPath(arena) catch return, r.start.line, r.start.character, text);
+ lsp.spanRow(out, handle.uri.toFsPath(arena) catch return, r.start.line, r.start.character, r.end.line, r.end.character, text);
}
}
diff --git a/src/main.zig b/src/main.zig
index 436cf938..1a2510de 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -142,7 +142,7 @@ fn nativeMain(init: std.process.Init) !void {
switch (@import("look.zig").resolve(a, std.mem.span(@as([*:0]u8, @ptrCast(cwd))), &realbuf)) {
.file => |t| {
opts.file = try arena.dupe(u8, t.path);
- opts.file_line = t.line;
+ opts.file_line = t.at.line;
},
.image => |t| opts.file = try arena.dupe(u8, t.path),
else => return error.BadArgs,
diff --git a/src/pardes.zig b/src/pardes.zig
index fbf48f05..eb2c8402 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -3665,8 +3665,12 @@ pub const Pardes = struct {
/// path when it has a real file, else `@pN`.
/// find — the pane's DIRECTORY, walked like fd. Rows are bare paths.
/// grep — the CONTENTS of every file under every pane's directory,
- /// walked like `grep -R`. Rows are `path:LINE:COL text`, the path
- /// relative to THIS pane's directory (absolute for a hit outside it).
+ /// walked like `grep -R`. Rows are `path:LINE:COL-ENDCOL text`, the
+ /// path relative to THIS pane's directory (absolute for a hit outside
+ /// it).
+ /// Both searches that match TEXT name the match's whole span, so looking a
+ /// row — which is all n/N do — selects what matched rather than parking on
+ /// its first cell. Find's rows are bare paths and have nothing to span.
/// No matches = an empty buffer.
pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search) void {
const pane = p.panes[id] orelse return;
@@ -3720,8 +3724,10 @@ pub const Pardes = struct {
std.fmt.bufPrint(&idbuf, config.pane_addr ++ "{d}", .{id}) catch return;
for (pl.lines, 0..) |ln, i| {
const at = std.ascii.indexOfIgnoreCase(ln, pat) orelse continue;
- const row = std.fmt.allocPrint(arena, "{s}:{d}:{d} {s}\n", .{
- loc, i + 1, at + 1, std.mem.trimEnd(u8, ln, " \t"),
+ // the row names the MATCH's span, not just its first cell, so
+ // n/N land ON the hit with it selected (config.range_sep)
+ const row = std.fmt.allocPrint(arena, "{s}:{d}:{d}{c}{d} {s}\n", .{
+ loc, i + 1, at + 1, config.range_sep, at + pat.len, std.mem.trimEnd(u8, ln, " \t"),
}) catch return;
out.appendSlice(arena, row) catch return;
}
@@ -6058,18 +6064,19 @@ pub const Pardes = struct {
// ---- the ONE dispatcher: look (right/Enter) and execute (middle/Tab) ----
- /// Focus pane `id` and, for a nonzero 1-based `line`, put its modal cursor
- /// there (`col` likewise, 0 = line start): files recenter the view on it,
- /// terminals ride their scrollback to it. Both look targets that name a
- /// live pane land here — a path a pane already holds, and `@pN:LINE:COL`.
- pub fn focusPaneLine(p: *Pardes, id: usize, line: usize, col: usize) void {
+ /// Focus pane `id` and, for a nonzero 1-based `at.line`, put its modal
+ /// cursor there (`at.col` likewise, 0 = line start): files recenter the
+ /// view on it, terminals ride their scrollback to it. Both look targets
+ /// that name a live pane land here — a path a pane already holds, and
+ /// `@pN:LINE:COL`. A RANGED spot selects (selectSpan below).
+ pub fn focusPaneLine(p: *Pardes, id: usize, at: look.Spot) void {
if (id >= MAX_PANES) return;
const pane = p.panes[id] orelse return;
p.active = id;
- if (line == 0) return;
+ if (at.line == 0) return;
if (pane.file) |*f| {
- if (line > file_pane.nlines(p.gpa, f)) return;
- const next = (line - 1) -| pane.rows / 2; // center, clamp at top
+ if (at.line > file_pane.nlines(p.gpa, f)) return;
+ const next = (at.line - 1) -| pane.rows / 2; // center, clamp at top
if (next != f.scroll) {
f.scroll = next;
f.syntax_dirty = true;
@@ -6078,16 +6085,44 @@ pub const Pardes = struct {
// land the modal cursor on the target line (and keep
// ensureCursorVisible agreeing with the recenter — a stale cursor
// would yank the view right back)
- pane.cur_row = @intCast(line - 1);
- pane.cur_col = if (col > 0) @intCast(col - 1) else 0;
+ pane.cur_row = @intCast(at.line - 1);
+ pane.cur_col = if (at.col > 0) @intCast(at.col - 1) else 0;
pane.cur_pinned = true;
+ if (at.end_line != 0) return p.selectSpan(pane, at);
pane.ensureCursorVisible();
}
+ /// Select the span a RANGED look word names (config.range_sep): the two
+ /// ends are block-cursor CELLS, so this is the same cellRange/setPaneRange
+ /// pair every motion writes back through — the cursor lands on the span's
+ /// last cell with the anchor on its first, which is where helix leaves you
+ /// after a search too.
+ ///
+ /// EXPLICIT, so the acme chords act on it like a v/x selection: the whole
+ /// point of `n` selecting a hit is being able to chord the match straight
+ /// into the next command.
+ ///
+ /// Everything clamps, because a range is a claim about a file that may
+ /// have changed underneath it: hxOff pins a row past the end to the last
+ /// line and a column past the end to that line's terminator, so a stale
+ /// row selects what is still there instead of crashing or highlighting
+ /// garbage. A whole-lines range (no end column) runs to the terminator by
+ /// asking for a column no line can have, which is helix's own `x`.
+ fn selectSpan(p: *Pardes, pane: *Pane, at: look.Spot) void {
+ const pl = p.paneCursorLines(pane) catch return;
+ const text = p.flatSurface(pane, pl) catch return;
+ // saturating, not `- 1`: `f.zig:0-5` is a legal thing to type and a
+ // 1-based zero is the same nothing an absent number is
+ const acol: i32 = @intCast(at.col -| 1);
+ const ecol: i32 = if (at.end_col > 0) @intCast(at.end_col - 1) else std.math.maxInt(i32);
+ const r = cellRange(text, @intCast(at.line -| 1), acol, @intCast(at.end_line -| 1), ecol);
+ setPaneRange(pane, pl, text, r, true);
+ }
+
/// focus the pane already loaded on `path` (exact match), if any: file
/// panes recenter on a :NN line like the look dedup always has, image
/// panes just focus. Returns false when no pane holds that path.
- fn focusPaneByPath(p: *Pardes, path: []const u8, line: usize, col: usize) bool {
+ fn focusPaneByPath(p: *Pardes, path: []const u8, at: look.Spot) bool {
for (p.panes, 0..) |slot, i| {
const tt = slot orelse continue;
if (tt.image) |iv| if (std.mem.eql(u8, iv.path, path)) {
@@ -6096,7 +6131,7 @@ pub const Pardes = struct {
};
const ff = if (tt.file) |*f| f else continue;
if (!std.mem.eql(u8, ff.path, path)) continue;
- p.focusPaneLine(i, line, col);
+ p.focusPaneLine(i, at);
return true;
}
return false;
@@ -6126,7 +6161,7 @@ pub const Pardes = struct {
// canonical (realpath'd or dump-given), so match the word as-is
// here and joined onto each directory below.
const pl = look.parsePathLine(trimmed);
- if (pl.path.len > 0 and p.focusPaneByPath(pl.path, pl.line, pl.col)) return;
+ if (pl.path.len > 0 and p.focusPaneByPath(pl.path, pl.at)) return;
// The word is resolved against the pane DIRECTORIES in access
// order: the pane the click came from FIRST — its answer is the
// one taken, so nothing that resolves today moves — then every
@@ -6155,7 +6190,7 @@ pub const Pardes = struct {
if (pl.path.len > 0 and pl.path[0] != '/') {
var joinbuf: [2048]u8 = undefined;
if (std.fmt.bufPrint(&joinbuf, "{s}/{s}", .{ dir, pl.path }) catch null) |j|
- if (p.focusPaneByPath(j, pl.line, pl.col)) return;
+ if (p.focusPaneByPath(j, pl.at)) return;
}
found = look.resolve(txt, dir, &realbuf);
if (found != .none or pl.path.len == 0 or pl.path[0] == '/') break;
@@ -6175,7 +6210,7 @@ pub const Pardes = struct {
// `@p7:10:5`: pane 7, line 10, column 5 — how a search result
// points at a terminal or an output buffer, neither of which
// has a path.
- .pane => |t| p.focusPaneLine(t.id, t.line, t.col),
+ .pane => |t| p.focusPaneLine(t.id, t.at),
.url => |u| if (u.len <= 256) p.emit(.{ .open_link = .from(u) }),
.dir => |dir| {
// focus an existing terminal on this dir, else fork one below
@@ -6197,19 +6232,29 @@ pub const Pardes = struct {
},
.file => |target| {
// focus an existing pane on this path (rescrolled), else open
- if (p.focusPaneByPath(target.path, target.line, target.col)) return;
+ if (p.focusPaneByPath(target.path, target.at)) return;
const free = p.freeSlot() orelse return;
- const nt = file_pane.open(p, free, target.path, target.line) catch return;
- if (target.col > 0) nt.cur_col = @intCast(target.col - 1);
+ const nt = file_pane.open(p, free, target.path, target.at.line) catch return;
+ if (target.at.col > 0) nt.cur_col = @intCast(target.at.col - 1);
p.placeDoc(id, free, nt);
// center the target line: the pane's real body height only
// exists after placement, so lay out now and pull the
// scroll up by half a body (line 0 opens stay at the top)
p.computeGeom();
nt.file.?.scroll -|= @max(1, p.rects[free].h -| BOX_H) / 2;
+ // ...and only THEN select a range, restoring that scroll:
+ // setPaneRange keeps its cursor visible, and a pane this fresh
+ // has no true geometry yet for it to judge against (pane.rows
+ // is only refreshed in sync), so the centering just computed is
+ // the answer and ensureCursorVisible's is not.
+ if (target.at.end_line != 0) {
+ const centered = nt.file.?.scroll;
+ p.selectSpan(nt, target.at);
+ nt.file.?.scroll = centered;
+ }
},
.image => |target| {
- if (p.focusPaneByPath(target.path, 0, 0)) return;
+ if (p.focusPaneByPath(target.path, .{})) return;
const free = p.freeSlot() orelse return;
// petscii by default when the host has no kitty graphics
const nt = p.openImageView(free, target.path, !p.kitty_ok) catch return;
@@ -6716,7 +6761,7 @@ pub const Pardes = struct {
if (p.njumps == 0 or next < 0 or next >= p.njumps) return;
p.jcur = @intCast(next);
const j = p.jumps[p.jcur];
- p.focusPaneLine(j.pane, j.line, j.col);
+ p.focusPaneLine(j.pane, .{ .line = j.line, .col = j.col });
}
/// Recompute geometry, push grid-size changes to each emulator + pty, fire
diff --git a/src/tutor.txt b/src/tutor.txt
index 2d0e951a..ee6f9f79 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -166,11 +166,16 @@
pane's directory searches for it — acme's button 3 — except in tty mode,
where the click belongs to the program on the other end. On a shell with no
search armed, n/N instead step the lookable tokens in its output.
- A hit in a file reads `path:LINE:COL`, the ordinary look target. A hit in
- a shell or an output buffer has no file to name, so it reads `@pN:LINE:COL`
- — pane N, line LINE, column COL. Looking either one goes there; the column
- is optional (`main.zig:100`, `@p3:12`) and you can type one yourself
- anywhere text lives.
+ A hit in a file reads `path:LINE:COL-ENDCOL`, the ordinary look target
+ carrying the SPAN that matched. A hit in a shell or an output buffer has no
+ file to name, so it reads `@pN:LINE:COL-ENDCOL` — pane N, then the place in
+ it. Looking either one goes there and SELECTS the span, which is why n/N
+ land ON a hit rather than beside it.
+ The range is part of the PATH syntax and not part of search: type one
+ anywhere text lives and a look on it selects. `main.zig:412-418` is whole
+ lines, `main.zig:412:9-21` is columns on one line, `main.zig:412:9-418:1`
+ is the general form, and the shorter spellings still mean what they always
+ did — `main.zig:100`, `main.zig:100:7`, `@p3:12`, all of them optional.
FIND: the "Find" builtin (SPC f f) arms the same tag input, but Enter
walks the pane's DIRECTORY instead of its text — `fd`, in-core — and writes
one matching PATH per row into the same "+Search" buffer. Rows are look
diff --git a/test/snapshots/fsearch.golden b/test/snapshots/fsearch.golden
index 02176d84..d89cd743 100644
--- a/test/snapshots/fsearch.golden
+++ b/test/snapshots/fsearch.golden
@@ -88,10 +88,10 @@
|
|
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 1 g.txt:2:1 MARK a
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
-== snap first grid=180x30 cursor=7,3
+| 1 g.txt:2:1-4 MARK a
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
+== snap first grid=180x30 cursor=10,3
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
| 1 l1
@@ -119,14 +119,14 @@
|
|
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 1 g.txt:2:1 MARK a
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
+| 1 g.txt:2:1-4 MARK a
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
== style first
|0: 0-52 #949494,#121212, 53-179 d,#121212,
|1: 0-1 d,#626262, 2-48 #949494,#121212, 49-59 d,#121212, 60-61 d,#262626, 62-97 #949494,#121212, 98-119 d,#121212, 120-121 d,#262626, 122-157 #949494,#121212, 158-179 d,#121212,
|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-8 #bdbdbd,#080808, 9-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
-|3: 0-1 d,#444444, 2-6 #949494,#121212, 7-12 #bdbdbd,#080808, 13-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #949494,#121212, 7-10 #d8c8a8,#4a3a1e, 11-12 #bdbdbd,#080808, 13-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-8 #bdbdbd,#080808, 9-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-8 #bdbdbd,#080808, 9-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-8 #bdbdbd,#080808, 9-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
@@ -151,9 +151,9 @@
|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
|26: 0-1 d,#262626, 2-45 #949494,#121212, 46-59 d,#121212, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
|27: 0-1 d,#444444, 2-6 #626262,#080808, 7-59 #d8c8a8,#4a3a1e, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
-|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
-|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-23 #bdbdbd,#080808, 24-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
-== snap second grid=180x30 cursor=7,7
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-24 #bdbdbd,#080808, 25-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
+|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-25 #bdbdbd,#080808, 26-59 d,#080808, 60-61 d,#444444, 62-119 #bdbdbd,#080808, 120-121 d,#444444, 122-179 #bdbdbd,#080808,
+== snap second grid=180x30 cursor=10,7
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
| 1 l1
@@ -181,10 +181,10 @@
|
|
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 1 g.txt:2:1 MARK a
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
-== snap third grid=180x30 cursor=7,11
+| 1 g.txt:2:1-4 MARK a
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
+== snap third grid=180x30 cursor=10,11
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
| 1 l1
@@ -212,10 +212,10 @@
|
|
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
| 4
-== snap back grid=180x30 cursor=7,7
+== snap back grid=180x30 cursor=10,7
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
| 1 l1
@@ -243,10 +243,10 @@
|
|
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 1 g.txt:2:1 MARK a
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
-== snap cancel grid=180x30 cursor=7,7
+| 1 g.txt:2:1-4 MARK a
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
+== snap cancel grid=180x30 cursor=10,7
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
| 1 l1
@@ -274,10 +274,10 @@
|
|
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 1 g.txt:2:1 MARK a
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
-== snap nomatch grid=180x30 cursor=7,7
+| 1 g.txt:2:1-4 MARK a
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
+== snap nomatch grid=180x30 cursor=10,7
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
| 1 l1
@@ -305,6 +305,6 @@
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del
-| 1 g.txt:2:1 MARK a
-| 2 g.txt:6:1 MARK b
-| 3 g.txt:10:1 MARK c
+| 1 g.txt:2:1-4 MARK a
+| 2 g.txt:6:1-4 MARK b
+| 3 g.txt:10:1-4 MARK c
diff --git a/test/snapshots/fsearch.snap b/test/snapshots/fsearch.snap
index d27d6f08..af1363dd 100644
--- a/test/snapshots/fsearch.snap
+++ b/test/snapshots/fsearch.snap
@@ -1,8 +1,9 @@
# `/` file search: the pattern types into the tag (persistent, no popup);
-# Enter searches the buffer in-core and writes `path:NNN: text` into a +Search
-# OUTPUT BUFFER (a file pane with no file behind it — no Save in its tag);
-# n/N select the next/prev row there and look it, so the file pane scrolls to
-# path:NNN. Esc abandons the input; a no-match search opens an EMPTY buffer
+# Enter searches the buffer in-core and writes `path:LINE:COL-ENDCOL text` into
+# a +Search OUTPUT BUFFER (a file pane with no file behind it — no Save in its
+# tag); n/N select the next/prev row there and look it, so the file pane
+# scrolls to the hit AND SELECTS IT — the row names the match's whole span, not
+# just where it starts. Esc abandons the input; a no-match search opens an EMPTY buffer
# (every search gets its own, stacked under the pane) and leaves n with
# nowhere to go.
file g.txt l1\nMARK a\nl3\nl4\nl5\nMARK b\nl7\nl8\nl9\nMARK c\nl11\nl12
@@ -30,10 +31,12 @@ text MARK
stable 400 5000
snap typing
key enter
-wait 10000 g.txt:2:1 MARK
+wait 10000 g.txt:2:1-4 MARK
stable 700 10000
snap results
-# n selects result 1 in the results buffer + scrolls the file to line 2
+# n selects result 1 in the results buffer, scrolls the file to line 2 and
+# selects the four cells the pattern matched there — the styles below are what
+# prove the second half of that
key n
stable 700 15000
snap first
diff --git a/test/snapshots/grep.golden b/test/snapshots/grep.golden
index 768c56dc..8ea45e14 100644
--- a/test/snapshots/grep.golden
+++ b/test/snapshots/grep.golden
@@ -88,10 +88,10 @@
|
|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 1 a.txt:1:7 hello TARGET one
-| 2 a.txt:3:6 last TARGET line
-| 3 sub/b.txt:2:1 TARGET inside sub
-== snap first grid=180x30 cursor=13,2
+| 1 a.txt:1:7-12 hello TARGET one
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
+== snap first grid=180x30 cursor=18,2
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/grep/cwd/a.txt Save Del NOR /tmp/pardes-snap/grep/cwd Del NOR /tmp/pardes-snap/grep/cwd Del
| 1 hello TARGET one
@@ -119,10 +119,10 @@
|
|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 1 a.txt:1:7 hello TARGET one
-| 2 a.txt:3:6 last TARGET line
-| 3 sub/b.txt:2:1 TARGET inside sub
-== snap opened-hit grid=180x30 cursor=12,4
+| 1 a.txt:1:7-12 hello TARGET one
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
+== snap opened-hit grid=180x30 cursor=17,4
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/grep/cwd/a.txt Save Del NOR /tmp/pardes-snap/grep/cwd Del NOR /tmp/pardes-snap/grep/cwd Del
| 1 hello TARGET one
@@ -150,10 +150,10 @@
|
|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 2 a.txt:3:6 last TARGET line
-| 3 sub/b.txt:2:1 TARGET inside sub
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
| 4
-== snap nomatch grid=180x30 cursor=13,2
+== snap nomatch grid=180x30 cursor=17,4
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/grep/cwd/a.txt Save Del NOR /tmp/pardes-snap/grep/cwd Del NOR /tmp/pardes-snap/grep/cwd Del
| 1 hello TARGET one
@@ -163,9 +163,9 @@
|
| a.txt
|
-| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 1 a.txt:1:7 hello TARGET one
-| 2 a.txt:3:6 last TARGET line
+|
+|
+|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/grep/cwd/sub/b.txt Save Del
@@ -181,8 +181,8 @@
|
|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 2 a.txt:3:6 last TARGET line
-| 3 sub/b.txt:2:1 TARGET inside sub
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
| 4
== snap chordsel grid=180x30 cursor=19,2
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
@@ -194,9 +194,9 @@
|
| a.txt
|
-| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 1 a.txt:1:7 hello TARGET one
-| 2 a.txt:3:6 last TARGET line
+|
+|
+|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/grep/cwd/sub/b.txt Save Del
@@ -212,8 +212,8 @@
|
|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 2 a.txt:3:6 last TARGET line
-| 3 sub/b.txt:2:1 TARGET inside sub
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
| 4
== snap chord grid=180x30 cursor=19,2
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
@@ -221,13 +221,13 @@
| 1 hello TARGET one
| 2 nothing here a.txt noise.txt sub a.txt noise.txt sub
| 3 last TARGET line
+|
+|
+| a.txt
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 1 a.txt:1:7 hello TARGET one
-| 2 a.txt:3:6 last TARGET line a.txt
-| 3 sub/b.txt:2:1 TARGET inside sub
-| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 1 a.txt:1:7 hello TARGET one
-| 2 a.txt:3:6 last TARGET line
+| 1 a.txt:1:7-12 hello TARGET one
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/grep/cwd/sub/b.txt Save Del
@@ -243,6 +243,6 @@
|
|
| NOR /tmp/pardes-snap/grep/cwd/+Search Del
-| 2 a.txt:3:6 last TARGET line
-| 3 sub/b.txt:2:1 TARGET inside sub
+| 2 a.txt:3:6-11 last TARGET line
+| 3 sub/b.txt:2:1-6 TARGET inside sub
| 4
diff --git a/test/snapshots/grep.snap b/test/snapshots/grep.snap
index 12ff2c70..af05f2d8 100644
--- a/test/snapshots/grep.snap
+++ b/test/snapshots/grep.snap
@@ -2,7 +2,8 @@
# SAME tag input `/` and Find use, but the walk reads file CONTENTS under every
# pane's directory at once — minus any directory another pane's already
# contains, so a tree two panes sit in is searched ONCE and every hit appears
-# once. A row is `path:LINE:COL text` with the path RELATIVE to the directory
+# once. A row is `path:LINE:COL-ENDCOL text` — the match's whole span, so
+# looking it SELECTS the hit — with the path RELATIVE to the directory
# of the pane that asked — the same directory its results buffer is named in,
# so the short name resolves; only a hit from outside that tree stays absolute.
# n/N walk the rows like any other result list, and a row pointing into a file
@@ -43,7 +44,8 @@ key enter
wait 10000 sub/b.txt:2:1
stable 700 15000
snap results
-# n looks the first row: absolute path, line 1, column 7 (the `T`)
+# n looks the first row: absolute path, line 1, columns 7..12 (`TARGET`), and
+# those six cells come up selected
key n
stable 700 15000
snap first
@@ -54,9 +56,16 @@ key n
wait 10000 TARGET inside sub
stable 700 15000
snap opened-hit
-# a pattern nothing holds: an empty list of its own, n has nowhere to go
-press middle 45 1
-release middle 45 1
+# a pattern nothing holds: an empty list of its own, n has nowhere to go.
+# Armed from the LEADER, not the topbar word: the n above left the hit
+# SELECTED in a.txt (a ranged result row is one a look selects), and a topbar
+# execute chords whatever is selected into its argument — so a middle-click
+# here would run `Grep TARGET` and never ask for a pattern at all. The leader
+# path runs the builtin with no argument, which is what this step is about; the
+# chord itself is the last two snaps below.
+key space
+key f
+key g
stable 400 5000
text zzqq
key enter
diff --git a/test/snapshots/lookrange.golden b/test/snapshots/lookrange.golden
new file mode 100644
index 00000000..f8b5795e
--- /dev/null
+++ b/test/snapshots/lookrange.golden
@@ -0,0 +1,496 @@
+== snap opened grid=150x30 cursor=7,2
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap wholelines grid=150x30 cursor=11,21
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 1 aaaa
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+|
+|
+== style wholelines
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #949494,#121212, 7-11 #d8c8a8,#4a3a1e, 12-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #626262,#080808, 7-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+== snap onelinespan grid=150x30 cursor=15,23
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 1 aaaa
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+|
+|
+== style onelinespan
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #949494,#121212, 7-9 #bdbdbd,#080808, 10-15 #d8c8a8,#4a3a1e, 16-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+== snap fullspan grid=150x30 cursor=9,21
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 1 aaaa
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+|
+|
+== style fullspan
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-7 #bdbdbd,#080808, 8-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #949494,#121212, 7-9 #d8c8a8,#4a3a1e, 10-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #626262,#080808, 7-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+== snap stale grid=150x30 cursor=10,24
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+|
+|
+|
+== style stale
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#121212, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #626262,#080808, 7-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #626262,#080808, 7-11 #bdbdbd,#080808, 12-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #949494,#121212, 7-10 #d8c8a8,#4a3a1e, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+== snap results grid=150x30 cursor=10,24
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/+Search Del
+| 1 r.txt:7:4-9 gg NEEDLE gg
+| 2 r.txt:8:6-11 hhhh NEEDLE
+== snap hit1 grid=150x30 cursor=15,22
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/+Search Del
+| 1 r.txt:7:4-9 gg NEEDLE gg
+| 2 r.txt:8:6-11 hhhh NEEDLE
+== style hit1
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#121212, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #949494,#121212, 7-9 #bdbdbd,#080808, 10-15 #d8c8a8,#4a3a1e, 16-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#262626, 2-47 #949494,#121212, 48-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-30 #bdbdbd,#080808, 31-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+== snap hit2 grid=150x30 cursor=17,22
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/+Search Del
+| 1 r.txt:7:4-9 gg NEEDLE gg
+| 2 r.txt:8:6-11 hhhh NEEDLE
+== style hit2
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#121212, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#121212, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #626262,#080808, 7-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #949494,#121212, 7-11 #bdbdbd,#080808, 12-17 #d8c8a8,#4a3a1e, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#262626, 2-47 #949494,#121212, 48-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-30 #bdbdbd,#080808, 31-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+== snap back grid=150x30 cursor=15,22
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| NOR /tmp/pardes-snap/lookrange/cwd/look.txt Save Del NOR /tmp/pardes-snap/lookrange/cwd Del
+| 1 r.txt:3-5
+| 2 r.txt:7:4-9 look.txt r.txt
+| 3 r.txt:3:2-5:3
+| 4 r.txt:8:6-99:9
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/r.txt Save Del
+| 2 bbbb
+| 3 cccc dccc
+| 4 dddd
+| 5 eeee
+| 6 ffff
+| 7 gg NEEDLE gg
+| 8 hhhh NEEDLE
+| 9 iiii
+|
+|
+| NOR /tmp/pardes-snap/lookrange/cwd/+Search Del
+| 1 r.txt:7:4-9 gg NEEDLE gg
+| 2 r.txt:8:6-11 hhhh NEEDLE
+== style back
+|0: 0-52 #949494,#121212, 53-149 d,#121212,
+|1: 0-1 d,#262626, 2-53 #949494,#121212, 54-74 d,#121212, 75-76 d,#262626, 77-114 #949494,#121212, 115-149 d,#121212,
+|2: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|4: 0-1 d,#444444, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|5: 0-1 d,#444444, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|6: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|7: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|8: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|9: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|10: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|11: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|12: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|13: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|14: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|15: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|16: 0-1 d,#626262, 2-50 #949494,#121212, 51-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|17: 0-1 d,#121212, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|18: 0-1 d,#444444, 2-6 #626262,#080808, 7-15 #bdbdbd,#080808, 16-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|19: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|20: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|21: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|22: 0-1 d,#444444, 2-6 #949494,#121212, 7-9 #bdbdbd,#080808, 10-15 #d8c8a8,#4a3a1e, 16-18 #bdbdbd,#080808, 19-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|23: 0-1 d,#444444, 2-6 #626262,#080808, 7-17 #bdbdbd,#080808, 18-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|24: 0-1 d,#444444, 2-6 #626262,#080808, 7-10 #bdbdbd,#080808, 11-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|25: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|26: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|27: 0-1 d,#262626, 2-47 #949494,#121212, 48-74 d,#121212, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-74 #d8c8a8,#4a3a1e, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
+|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-30 #bdbdbd,#080808, 31-74 d,#080808, 75-76 d,#444444, 77-149 #bdbdbd,#080808,
diff --git a/test/snapshots/lookrange.snap b/test/snapshots/lookrange.snap
new file mode 100644
index 00000000..d1a86e0c
--- /dev/null
+++ b/test/snapshots/lookrange.snap
@@ -0,0 +1,70 @@
+# RANGED LOOK PATHS. `path:LINE:COL` says where to PARK; a range says what to
+# SELECT, and it belongs to the PATH (config.range_sep) rather than to the
+# search that is merely the first thing writing one. So this script right-
+# clicks the three spellings as ordinary words sitting in a file:
+# r.txt:3-5 whole lines 3 through 5
+# r.txt:7:4-9 line 7, columns 4 through 9
+# r.txt:3:2-5:3 line 3 column 2 through line 5 column 3
+# ...and only then presses `/`, whose result rows are exactly this syntax —
+# which is how n/N step to a hit with the matched text selected.
+#
+# `snapstyle` is the assertion that matters: the grid TEXT after a ranged look
+# is the same text a plain `:LINE:COL` look leaves, so the selection only shows
+# in the styles.
+file r.txt aaaa\nbbbb\ncccc dccc\ndddd\neeee\nffff\ngg NEEDLE gg\nhhhh NEEDLE\niiii
+file look.txt r.txt:3-5\nr.txt:7:4-9\nr.txt:3:2-5:3\nr.txt:8:6-99:9
+start 30 150 look.txt
+wait 8000 r.txt:3-5
+stable 700 20000
+snap opened
+# --- form 1: whole lines. r.txt opens BELOW look.txt (an ordinary look) with
+# lines 3, 4 and 5 selected whole, terminators included — helix's own `x`.
+press right 10 3
+release right 10 3
+wait 10000 cccc dccc
+stable 700 15000
+snap wholelines
+snapstyle wholelines
+# --- form 2: one line, a column span. r.txt is open now, so this re-uses that
+# pane (focusPaneByPath) — the same route a `/` result row takes.
+press right 12 4
+release right 12 4
+stable 700 15000
+snap onelinespan
+snapstyle onelinespan
+# --- form 3: the general one, across both lines and columns
+press right 12 5
+release right 12 5
+stable 700 15000
+snap fullspan
+snapstyle fullspan
+# --- a STALE range: line 99 of a nine-line file, which is what a result row
+# looks like once the file has changed underneath it. It must CLAMP — no crash,
+# no garbage highlight — landing on the last cell that really exists.
+press right 12 6
+release right 12 6
+stable 700 15000
+snap stale
+snapstyle stale
+# --- and now the first consumer. The look above left r.txt focused, so `/`
+# arms on it: the rows it writes carry the match's span, and n/N therefore step
+# to each hit with the word itself selected.
+key /
+text NEEDLE
+key enter
+wait 10000 r.txt:7:4-9 gg NEEDLE
+stable 700 15000
+snap results
+key n
+stable 700 15000
+snap hit1
+snapstyle hit1
+key n
+stable 700 15000
+snap hit2
+snapstyle hit2
+# N walks back onto the first hit, still selected
+key N
+stable 700 15000
+snap back
+snapstyle back
diff --git a/test/snapshots/lsp.golden b/test/snapshots/lsp.golden
index 32d5b6d0..85c5c9ce 100644
--- a/test/snapshots/lsp.golden
+++ b/test/snapshots/lsp.golden
@@ -80,7 +80,7 @@
|
|
|
-== snap definition grid=120x40 cursor=10,4
+== snap definition grid=120x40 cursor=15,4
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/lsp/cwd/a.zig Save Del NOR /tmp/pardes-snap/lsp/cwd Del
| 1 const std = @import("std");
@@ -121,7 +121,7 @@
|
|
|
-== snap hover grid=120x40 cursor=10,4
+== snap hover grid=120x40 cursor=15,4
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/lsp/cwd/a.zig Save Del NOR /tmp/pardes-snap/lsp/cwd Del
| 1 const std = @import("std");
@@ -162,7 +162,7 @@
| NOR /tmp/pardes-snap/lsp/cwd/+Hover Del
| 1 fn helper(x: u32) u32
| 2 (fn (u32) u32)
-== snap symbols grid=120x40 cursor=10,4
+== snap symbols grid=120x40 cursor=15,4
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/lsp/cwd/a.zig Save Del NOR /tmp/pardes-snap/lsp/cwd Del
| 1 const std = @import("std");
@@ -197,13 +197,13 @@
|
|
| NOR /tmp/pardes-snap/lsp/cwd/+Search Del
-| 1 /tmp/pardes-snap/lsp/cwd/a.zig:1:7 std
-| 2 /tmp/pardes-snap/lsp/cwd/a.zig:3:4 helper fn helper(
-| 3 /tmp/pardes-snap/lsp/cwd/a.zig:7:8 main fn main() vo
+| 1 /tmp/pardes-snap/lsp/cwd/a.zig:1:7-9 std
+| 2 /tmp/pardes-snap/lsp/cwd/a.zig:3:4-9 helper fn helpe
+| 3 /tmp/pardes-snap/lsp/cwd/a.zig:7:8-11 main fn main()
| NOR /tmp/pardes-snap/lsp/cwd/+Hover Del
| 1 fn helper(x: u32) u32
| 2 (fn (u32) u32)
-== snap step grid=120x40 cursor=13,2
+== snap step grid=120x40 cursor=15,2
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/lsp/cwd/a.zig Save Del NOR /tmp/pardes-snap/lsp/cwd Del
| 1 const std = @import("std");
@@ -238,9 +238,9 @@
|
|
| NOR /tmp/pardes-snap/lsp/cwd/+Search Del
-| 1 /tmp/pardes-snap/lsp/cwd/a.zig:1:7 std
-| 2 /tmp/pardes-snap/lsp/cwd/a.zig:3:4 helper fn helper(
-| 3 /tmp/pardes-snap/lsp/cwd/a.zig:7:8 main fn main() vo
+| 1 /tmp/pardes-snap/lsp/cwd/a.zig:1:7-9 std
+| 2 /tmp/pardes-snap/lsp/cwd/a.zig:3:4-9 helper fn helpe
+| 3 /tmp/pardes-snap/lsp/cwd/a.zig:7:8-11 main fn main()
| NOR /tmp/pardes-snap/lsp/cwd/+Hover Del
| 1 fn helper(x: u32) u32
| 2 (fn (u32) u32)
@@ -279,9 +279,9 @@
|
|
| NOR /tmp/pardes-snap/lsp/cwd/+Search Del
-| 1 /tmp/pardes-snap/lsp/cwd/a.zig:1:7 std
-| 2 /tmp/pardes-snap/lsp/cwd/a.zig:3:4 helper fn helper(
-| 3 /tmp/pardes-snap/lsp/cwd/a.zig:7:8 main fn main() vo
+| 1 /tmp/pardes-snap/lsp/cwd/a.zig:1:7-9 std
+| 2 /tmp/pardes-snap/lsp/cwd/a.zig:3:4-9 helper fn helpe
+| 3 /tmp/pardes-snap/lsp/cwd/a.zig:7:8-11 main fn main()
| NOR /tmp/pardes-snap/lsp/cwd/+Hover Del
| 1 fn helper(x: u32) u32
| 2 (fn (u32) u32)
diff --git a/test/snapshots/lsp.snap b/test/snapshots/lsp.snap
index a1cc056c..79acaee8 100644
--- a/test/snapshots/lsp.snap
+++ b/test/snapshots/lsp.snap
@@ -21,7 +21,8 @@ release left 18 10
stable 400 5000
snap cursor
# gd on a call: exactly one location comes back, so it JUMPS rather than
-# opening a picker — the cursor lands on the definition's name token, line 3.
+# opening a picker — and lands SELECTING the definition's name token on line 3,
+# because ZLS answers with a range and a ranged row is one a look selects.
# The jumplist is pinned first, so ctrl-o comes back.
key g
key d
diff --git a/test/snapshots/lspdebug.golden b/test/snapshots/lspdebug.golden
index bb0fee41..67421629 100644
--- a/test/snapshots/lspdebug.golden
+++ b/test/snapshots/lspdebug.golden
@@ -43,7 +43,7 @@
|
|
|
-== snap ctrlclick grid=120x44 cursor=10,4
+== snap ctrlclick grid=120x44 cursor=15,4
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/lspdebug/cwd/z.zig Save Del NOR /tmp/pardes-snap/lspdebug/cwd Del
| 1 const std = @import("std");
diff --git a/test/snapshots/psearch.golden b/test/snapshots/psearch.golden
index 2bd9fc81..7456db00 100644
--- a/test/snapshots/psearch.golden
+++ b/test/snapshots/psearch.golden
@@ -88,10 +88,10 @@
|
|
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
-== snap first grid=140x30 cursor=4,3
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap first grid=140x30 cursor=7,3
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/psearch/cwd Del
|
@@ -119,14 +119,14 @@
|
|
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
== style first
|0: 0-52 #949494,#121212, 53-139 d,#121212,
|1: 0-1 d,#626262, 2-37 #949494,#121212, 38-139 d,#121212,
|2: 0-1 d,#444444, 2-139 #bdbdbd,#080808,
-|3: 0-1 d,#444444, 2-139 #bdbdbd,#080808,
+|3: 0-1 d,#444444, 2-3 #bdbdbd,#080808, 4-7 #d8c8a8,#4a3a1e, 8-139 #bdbdbd,#080808,
|4: 0-1 d,#444444, 2-139 #bdbdbd,#080808,
|5: 0-1 d,#444444, 2-139 #bdbdbd,#080808,
|6: 0-1 d,#444444, 2-139 #bdbdbd,#080808,
@@ -151,9 +151,9 @@
|25: 0-1 d,#444444, 2-139 #bdbdbd,#080808,
|26: 0-1 d,#262626, 2-45 #949494,#121212, 46-139 d,#121212,
|27: 0-1 d,#444444, 2-6 #626262,#080808, 7-139 #d8c8a8,#4a3a1e,
-|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-139 d,#080808,
-|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-139 d,#080808,
-== snap second grid=140x30 cursor=4,5
+|28: 0-1 d,#444444, 2-6 #626262,#080808, 7-24 #bdbdbd,#080808, 25-139 d,#080808,
+|29: 0-1 d,#121212, 2-6 #626262,#080808, 7-24 #bdbdbd,#080808, 25-139 d,#080808,
+== snap second grid=140x30 cursor=7,5
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/psearch/cwd Del
|
@@ -181,10 +181,10 @@
|
|
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
-== snap back grid=140x30 cursor=4,3
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap back grid=140x30 cursor=7,3
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/psearch/cwd Del
|
@@ -212,10 +212,10 @@
|
|
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
-== snap nomatch grid=140x30 cursor=4,3
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap nomatch grid=140x30 cursor=7,3
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/psearch/cwd Del
|
@@ -243,9 +243,9 @@
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
== snap ttyinert grid=140x30 cursor=4,8
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| TTY /tmp/pardes-snap/psearch/cwd Del
@@ -274,10 +274,10 @@
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
-== snap rightsearch grid=140x30 cursor=4,3
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap rightsearch grid=140x30 cursor=7,3
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/psearch/cwd Del
|
@@ -299,12 +299,12 @@
|
|
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
| 1
| NOR /tmp/pardes-snap/psearch/cwd/+Search Del
-| 1 @p0:2:3 x MARK a
-| 2 @p0:4:3 x MARK b
-| 3 @p0:6:3 x MARK c
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
diff --git a/test/snapshots/psearch.snap b/test/snapshots/psearch.snap
index fb721c7d..e2ea1aea 100644
--- a/test/snapshots/psearch.snap
+++ b/test/snapshots/psearch.snap
@@ -1,9 +1,10 @@
# `/` search on a TERMINAL pane: the same tag input as a file pane, searching
# the shell's own scrollback (the plain text view Look reads, prompt rows
# hidden). Results land in a +Search OUTPUT BUFFER — a file pane with no file
-# behind it, so its tag has no Save — and point at `@p0:LINE`, the look target
-# that names a live pane, since a terminal has no path. n/N walk the buffer
-# and look each row, scrolling the terminal to that line of its scrollback.
+# behind it, so its tag has no Save — and point at `@p0:LINE:COL-ENDCOL`, the
+# look target that names a live pane, since a terminal has no path. n/N walk
+# the buffer and look each row, scrolling the terminal to that line of its
+# scrollback and selecting the span the row names.
# PLACEMENT, and this script is the proof: an output buffer is NOT a document
# — it is the result list of the pane that asked for it — so even here, with no
# doc pane in the session for it to join, it does NOT take a column of its own.
@@ -34,8 +35,9 @@ key enter
wait 10000 @p0:2:3
stable 700 15000
snap results
-# n looks result 1: the terminal's cursor lands on that scrollback line, at
-# the matched COLUMN (3, past the leading "x ") — not the start of the line
+# n looks result 1: the terminal's cursor lands on that scrollback line, on
+# the matched SPAN (cols 3..6, past the leading "x ") — not the start of the
+# line, and selected rather than merely parked on
key n
stable 700 15000
snap first
diff --git a/test/snapshots/rsearch.golden b/test/snapshots/rsearch.golden
index ee2a1cb4..39b9f57d 100644
--- a/test/snapshots/rsearch.golden
+++ b/test/snapshots/rsearch.golden
@@ -58,8 +58,8 @@
|
|
| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del
-| 1 code.txt:1:5 run count_x now
-| 2 code.txt:3:1 count_x again
+| 1 code.txt:1:5-11 run count_x now
+| 2 code.txt:3:1-7 count_x again
== snap select grid=180x30 cursor=7,2
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/rsearch/cwd/code.txt Save Del NOR /tmp/pardes-snap/rsearch/cwd Del NOR /tmp/pardes-snap/rsearch/cwd Del
@@ -87,10 +87,10 @@
|
|
| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del
-| 1 code.txt:4:1 pick two words
+| 1 code.txt:4:1-8 pick two words
| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del
-| 1 code.txt:1:5 run count_x now
-| 2 code.txt:3:1 count_x again
+| 1 code.txt:1:5-11 run count_x now
+| 2 code.txt:3:1-7 count_x again
== snap path-open grid=180x30 cursor=7,16
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| NOR /tmp/pardes-snap/rsearch/cwd/code.txt Save Del NOR /tmp/pardes-snap/rsearch/cwd Del NOR /tmp/pardes-snap/rsearch/cwd Del
@@ -118,7 +118,7 @@
|
|
| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del
-| 1 code.txt:4:1 pick two words
+| 1 code.txt:4:1-8 pick two words
| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del
-| 1 code.txt:1:5 run count_x now
-| 2 code.txt:3:1 count_x again
+| 1 code.txt:1:5-11 run count_x now
+| 2 code.txt:3:1-7 count_x again
diff --git a/test/snapshots/rsearch.snap b/test/snapshots/rsearch.snap
index 738afc23..a6dcd167 100644
--- a/test/snapshots/rsearch.snap
+++ b/test/snapshots/rsearch.snap
@@ -28,7 +28,7 @@ snap opened
# file, so the click searches for it: the results list both lines containing it.
press right 15 3
release right 15 3
-wait 10000 code.txt:1:5 run count_x
+wait 10000 code.txt:1:5-11 run count_x
stable 700 15000
snap search
# right-SELECT `pick two` (line 4, sweep cols 8..15): a multi-word span passes
@@ -36,7 +36,7 @@ snap search
press right 8 6
drag right 15 6
release right 15 6
-wait 10000 code.txt:4:1 pick two
+wait 10000 code.txt:4:1-8 pick two
stable 700 15000
snap select
# right-CLICK the path `sub/dep.txt:3` (line 2, col 18): isFileChar keeps / . :