From d8b35f18817faaba340059cc2bd88ab3b3bc919b Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 12:33:29 -0300 Subject: addr takes a row's range, L:C-L2:C2 or L:C-C2, so a Recent, +Search or Jumplist row pastes in The rows spell a range L:C-L2:C2 (inclusive), which addr read as L:C then back L2 lines. After an L:C, a - and a number is now that range, through its last character; the docs set both spellings side by side. Co-Authored-By: Claude Opus 5.5 --- src/ninep/addr.zig | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'src') diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig index 9498df19..09ef2b66 100644 --- a/src/ninep/addr.zig +++ b/src/ninep/addr.zig @@ -110,6 +110,35 @@ pub const Addr = struct { } const q = clip(modal.runeOffsetAt(a.text, .{ .row = @intCast(n - 1), .col = @intCast(col - 1) })); r = .{ .q0 = q, .q1 = q }; + // The rows' range, `L:C-L2:C2` or `L:C-C2` (Recent, + // +Search, Jumplist): through its last character, + // inclusive, as the rows mean it -- so a row pastes in. + if (a.i + 1 < a.expr.len and a.expr[a.i] == '-' and a.expr[a.i + 1] >= '1' and a.expr[a.i + 1] <= '9') { + a.i += 1; + var n2: u64 = 0; + while (a.i < a.expr.len and a.expr[a.i] >= '0' and a.expr[a.i] <= '9') : (a.i += 1) + n2 = @min(n2 * 10 + (a.expr[a.i] - '0'), std.math.maxInt(u32)); + var end_line = n; + var end_col = n2; + if (a.i + 1 < a.expr.len and a.expr[a.i] == ':' and a.expr[a.i + 1] >= '0' and a.expr[a.i + 1] <= '9') { + a.i += 1; + end_line = n2; + end_col = 0; + while (a.i < a.expr.len and a.expr[a.i] >= '0' and a.expr[a.i] <= '9') : (a.i += 1) + end_col = @min(end_col * 10 + (a.expr[a.i] - '0'), std.math.maxInt(u32)); + } + if (end_line == 0 or end_line > lines or end_col == 0) { + a.err = if (end_col == 0) e_col_zero else e_range; + return null; + } + const last = modal.runeOffsetAt(a.text, .{ .row = @intCast(end_line - 1), .col = @intCast(end_col - 1) }); + const e = clip(@min(a.text.len, last + (std.unicode.utf8ByteSequenceLength(if (last < a.text.len) a.text[last] else 'x') catch 1))); + if (e < q) { + a.err = e_order; + return null; + } + r.q1 = e; + } continue; } r = a.number(r, @intCast(n), dir, size) orelse return null; @@ -348,6 +377,21 @@ test "an address is evaluated from the current one, as acme's are" { try testing.expectEqualStrings(e_order, th.wr(q, qaddr, "/a/,/b/").reply.ename); } +test "a row's range, L:C-L2:C2 or L:C-C2, is an address through its last character" { + const p = try th.withFile(testing.allocator, "one\ntwo three\nfour\n"); + defer p.deinit(); + const addr = Node.of(th.serialOf(p), .addr); + const data = Node.of(th.serialOf(p), .xdata); + try testing.expectEqual(Status.ok, th.wr(p, addr, "2:5-2:9").reply.status); + try testing.expectEqualStrings("three", th.rd(p, data, 0, 64).bytes); + try testing.expectEqual(Status.ok, th.wr(p, addr, "2:1-3").reply.status); + try testing.expectEqualStrings("two", th.rd(p, data, 0, 64).bytes); + try testing.expectEqual(Status.ok, th.wr(p, addr, "1:2-3:2").reply.status); + try testing.expectEqualStrings("ne\ntwo three\nfo", th.rd(p, data, 0, 64).bytes); + try testing.expectEqualStrings(e_order, th.wr(p, addr, "2:5-1:1").reply.ename); + try testing.expectEqualStrings(e_range, th.wr(p, addr, "2:1-9:1").reply.ename); +} + test "an empty match at the search's start is passed over, as sam's nextmatch does" { const p = try th.withFile(testing.allocator, "ab\ncd\nef\n"); defer p.deinit(); -- cgit v1.3