From 28e90e064bd317d0e253edb2f5b305a6192f560b Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 01:00:42 -0300 Subject: A bare :N or :N:M look addresses the pane itself, its line N (column M), from every way a look comes; a PDF's :0 has no page 0 A look of `:12` had no path to resolve, so it fell through to a word search for the text ":12" and found nothing. A digit-led address with no path now names the looking pane, as `:/re/` already did: its look, the root's (the keyboard's pane), Look, B3 and an event write-back all reach it, a terminal's logical lines included. On a PDF, :0 is the has-no-page miss rather than a refusal. Co-Authored-By: Claude Opus 5.5 --- src/look.zig | 11 +++++++---- src/ninep/ctl.zig | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/look.zig b/src/look.zig index 922504c0..105b7717 100644 --- a/src/look.zig +++ b/src/look.zig @@ -1357,9 +1357,11 @@ fn addressed(tok: []const u8) ?struct { path: []const u8, expr: []const u8 } { if (std.mem.indexOfAny(u8, tok[0..colon], " \t") != null) return null; if (std.mem.indexOfAny(u8, expr, " \t") != null and std.mem.indexOfAny(u8, expr, "/?") == null) return null; if (expr.len == 0 or std.mem.indexOfScalar(u8, "0123456789+-/?$.#,;", expr[0]) == null) return null; + // `file:12` is a path's line, looked at as a path; a bare `:12` (`:12:5`) + // has no path, and is this pane's line, an address like any other. if (std.ascii.isDigit(expr[0])) { const pl = parsePathLine(tok); - if (pl.at.line != 0 and pl.end == tok.len) return null; + if (pl.at.line != 0 and pl.end == tok.len and pl.path.len > 0) return null; } return .{ .path = tok[0..colon], .expr = expr }; } @@ -1441,7 +1443,7 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void { const pane = p.panes[target] orelse return; const f = pane.file orelse { if (comptime pdf_enabled) if (pane.pdf) |*pv| return lookPdfPage(p, id, target, pv, expr); - if (by_serial and pane.isTerminal()) return lookTerminalAddress(p, id, target, expr); + if ((by_serial or path.len == 0) and pane.isTerminal()) return lookTerminalAddress(p, id, target, expr); // Named as the pane is, the bare `:addr` (path empty) included. var serial_name: [32]u8 = undefined; const subject = if (pane.image) |iv| iv.path else if (path.len > 0) path else std.fmt.bufPrint(&serial_name, config.pane_addr ++ "{d}", .{pane.serial}) catch "the pane"; @@ -1469,8 +1471,9 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void { /// other address is refused with what it takes. fn lookPdfPage(p: *Pardes, id: usize, target: usize, pv: anytype, expr: []const u8) void { var cut: [miss_path_cap + "…".len]u8 = undefined; - const page = std.fmt.parseInt(usize, std.mem.trim(u8, expr, " \t"), 10) catch 0; - if (page == 0) return missed(p, id, "{s}: a PDF has no text to address; its address is a page, :N", .{missText(pv.path, &cut)}); + const page = std.fmt.parseInt(usize, std.mem.trim(u8, expr, " \t"), 10) catch + return missed(p, id, "{s}: a PDF has no text to address; its address is a page, :N", .{missText(pv.path, &cut)}); + if (page == 0) return missedPage(p, id, pv.path, 0); if (page > pv.page_count) return missedPage(p, id, pv.path, page); const pane = p.panes[target].?; clearNavigationSelection(p, pane); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index cdb6092a..fd3418a1 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -1823,6 +1823,25 @@ test "Pager is a setting of the root ctl: pardes by default, off taken, anything try testing.expectEqualStrings("bad value in control message; takes pardes, off \"Pager\"", wr(p, root_ctl, "Pager less\n").reply.ename); } +test "a bare :N or :N:M look addresses the pane itself, from its look, the root's and event write-back" { + const p = try withFile(testing.allocator, "alpha\nbeta\ngamma\n"); + defer p.deinit(); + const serial = serialOf(p); + const pane = p.panes[p.paneBySerial(serial).?].?; + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .look), ":2\n").reply.status); + try testing.expectEqual(pane_files.State.Range{ .q0 = 6, .q1 = 11 }, pane_files.dotOf(pane)); + try testing.expectEqual(Status.ok, wr(p, root_look, ":3\n").reply.status); + try testing.expectEqual(pane_files.State.Range{ .q0 = 11, .q1 = 17 }, pane_files.dotOf(pane)); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .look), ":3:2\n").reply.status); + try testing.expectEqual(@as(u32, 12), pane_files.dotOf(pane).q0); + // Written back by an event reader, the same. + const event = Node.of(serial, .event); + const h = call(p, .{ .tag = 1, .op = .open, .node = event }).reply.handle; + defer _ = call(p, .{ .tag = 9, .op = .release, .node = event, .handle = h }); + try testing.expectEqual(Status.ok, wr(p, event, "ML0 0 0 2 :1\n").reply.status); + try testing.expectEqual(pane_files.State.Range{ .q0 = 0, .q1 = 6 }, pane_files.dotOf(pane)); +} + test "a refused Del written back through event fails that write, EIO, with its err" { const gpa = testing.allocator; const p = try withFile(gpa, "text\n"); -- cgit v1.3