summaryrefslogtreecommitdiff
path: root/src/look.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 07:11:20 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit9ecabb15b648237cca94253a7abc4b556805fde0 (patch)
tree973c4956d23e332ba8336499c7dc87156d5eb6e0 /src/look.zig
parentee316b57d58c91e103210b1adc428f618f630002 (diff)
downloadpardes-9ecabb15b648237cca94253a7abc4b556805fde0.tar.gz
pardes-9ecabb15b648237cca94253a7abc4b556805fde0.zip
`@p<terminal>:<address>` takes any address over the terminal's logical lines
Only a line number reached a terminal (the line way); any other address went to lookAddress, which wanted a file and said the terminal was no text. The body is now the lines a +Search of it lists, addressed from the cursor, and the match selected as a +Search row's range is. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/look.zig')
-rw-r--r--src/look.zig45
1 files changed, 44 insertions, 1 deletions
diff --git a/src/look.zig b/src/look.zig
index 28986552..cfebd42d 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1300,7 +1300,10 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void {
if (target == before and target != id) return;
}
const pane = p.panes[target] orelse return;
- const f = pane.file orelse return missed(p, id, "{s} is no text to address", .{path[0..@min(path.len, 64)]});
+ const f = pane.file orelse {
+ if (by_serial and pane.isTerminal()) return lookTerminalAddress(p, id, target, expr);
+ return missed(p, id, "{s} is no text to address", .{path[0..@min(path.len, 64)]});
+ };
if (!by_serial and path.len > 0 and (f.output != null or !std.mem.endsWith(u8, f.path, std.fs.path.basename(path)))) return;
var a: Addr = .{ .text = f.content, .lim = null, .expr = expr };
const r = a.address(ninep_pane.dotOf(pane)) orelse {
@@ -1314,6 +1317,46 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void {
p.active = target;
}
+/// `@p<terminal>:<address>`: any address over the terminal's body as its
+/// logical lines read (the lines a +Search lists as `@p<serial>:<line>`),
+/// from its cursor; the match is selected there, as a +Search row's is.
+fn lookTerminalAddress(p: *Pardes, id: usize, target: usize, expr: []const u8) void {
+ const Addr = @import("ninep/addr.zig").Addr;
+ const pane = p.panes[target].?;
+ const lines = edit.paneCursorLines(p, &pane.body) catch return missed(p, id, "{s}", .{"out of memory"});
+ var text: std.ArrayList(u8) = .empty;
+ const arena = p.scratch.allocator();
+ const row: usize = @intCast(@max(0, pane.body.cur_row));
+ var dot: u32 = 0;
+ for (lines, 0..) |ln, i| {
+ if (i == row) dot = @intCast(text.items.len + @min(ln.len, @as(usize, @intCast(@max(0, pane.body.cur_col)))));
+ text.appendSlice(arena, ln) catch return;
+ text.append(arena, '\n') catch return;
+ }
+ var a: Addr = .{ .text = text.items, .lim = null, .expr = expr };
+ const r = a.address(.{ .q0 = dot, .q1 = dot }) orelse return missed(p, id, "{s}", .{a.err});
+ if (a.i < expr.len or r.q0 > r.q1)
+ return missed(p, id, "{s}", .{if (r.q0 > r.q1) "addresses out of order" else "bad address syntax"});
+ const from = spotAt(text.items, r.q0);
+ var at: Spot = .{ .line = from.line, .col = from.col };
+ if (r.q1 > r.q0) {
+ // The last byte of the match, 1-based and inclusive, as a row's range.
+ const to = spotAt(text.items, r.q1 - 1);
+ at.end_line = to.line;
+ at.end_col = to.col;
+ }
+ clearNavigationSelection(p, pane);
+ focusPaneLine(p, target, at, .center);
+}
+
+/// The 1-based line and column of byte `q` of `text`.
+fn spotAt(text: []const u8, q: u32) struct { line: usize, col: usize } {
+ const upto = text[0..@min(q, text.len)];
+ const line = std.mem.count(u8, upto, "\n") + 1;
+ const start = if (std.mem.lastIndexOfScalar(u8, upto, '\n')) |nl| nl + 1 else 0;
+ return .{ .line = line, .col = upto.len - start + 1 };
+}
+
pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
const pane = p.panes[id] orelse return;
p.noteHaptic(.look);