diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 07:11:20 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 9ecabb15b648237cca94253a7abc4b556805fde0 (patch) | |
| tree | 973c4956d23e332ba8336499c7dc87156d5eb6e0 | |
| parent | ee316b57d58c91e103210b1adc428f618f630002 (diff) | |
| download | pardes-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]>
| -rw-r--r-- | docs/fs.md | 8 | ||||
| -rw-r--r-- | src/look.zig | 45 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 19 |
3 files changed, 68 insertions, 4 deletions
@@ -466,9 +466,11 @@ unfinished was cut short. A fragment never runs on its own. A line or Edit block held past 1 MiB is refused (EINVAL). `@p<serial>:<address>` addresses the pane with that serial as `file:<address>` -does a file, with any sam address (`/re/`, `#n`, `$`, `0/re/`, a range); a -miss, and a serial no pane has, each log an `err`, and look reads back -empty. A look takes acme's addresses after a colon (editors/acme/look.c:450): a +does a file, with any sam address (`/re/`, `#n`, `$`, `0/re/`, a range), +a terminal too: its body is then the text of its logical lines (the lines +a `+Search` of it lists), addressed from its cursor, and the match is +selected. A miss, and a serial no pane has (with a line, `@p77:3`, as +with any other address), each log an `err`, and look reads back empty. A look takes acme's addresses after a colon (editors/acme/look.c:450): a line written to `look` as `file:/re/`, `file:#n`, `file:$` or any address opens (or finds) the file and selects what the address names. **The address is evaluated from the file's dot**, as acme's is: `file:/re/` 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); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 47ac2846..acb1b934 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -1969,3 +1969,22 @@ test "@p<serial>:<address> takes any sam address; a miss and an unknown serial a try testing.expectEqualStrings("", rd(p, root, 0, 64).bytes); } +test "@p<terminal>:<address> takes any address over the terminal's lines, selecting the match" { + const p = try th.withTerm(testing.allocator); + defer p.deinit(); + const serial = serialOf(p); + const id = p.paneBySerial(serial).?; + p.update(.{ .output = .{ .pane = @intCast(id), .bytes = "one\r\ntwo MARK three\r\n" } }); + const pane = p.panes[id].?; + const root = @intFromEnum(tree.TopFile.look); + var line: [64]u8 = undefined; + try testing.expectEqual(Status.ok, wr(p, root, try std.fmt.bufPrint(&line, "@p{d}:0/MARK/\n", .{serial})).reply.status); + try testing.expectEqual(@as(i32, 1), pane.body.cur_row); + try testing.expect(pane.body.vsel.active); + // #n lands on that byte: #5 is the `w` of `two`. + try testing.expectEqual(Status.ok, wr(p, root, try std.fmt.bufPrint(&line, "@p{d}:#5\n", .{serial})).reply.status); + try testing.expectEqual(@as(i32, 1), pane.body.cur_row); + try testing.expectEqual(@as(i32, 1), pane.body.cur_col); + _ = wr(p, root, try std.fmt.bufPrint(&line, "@p{d}:/zzqq/\n", .{serial})); + try testing.expect(th.logHas(p, "look: no match for regexp")); +} |
