diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 02:12:34 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 69b6d714b7fc7788a1bf1c2305262a6e861253e8 (patch) | |
| tree | 2ad39a1ec58a25c4b4e65b517ba29ea86dc5a3a5 /src/ninep/addr.zig | |
| parent | 3db73735e9a8d88878bc9ee343f226c033320913 (diff) | |
| download | pardes-69b6d714b7fc7788a1bf1c2305262a6e861253e8.tar.gz pardes-69b6d714b7fc7788a1bf1c2305262a6e861253e8.zip | |
A line address past the last newline is sam's empty line there
Forward line addresses followed acme's number(), which refuses the empty
line after a text's last newline, so 1 of an empty buffer and 2 of "a\n"
were out of range and Edit 1i/header/ failed on an empty file. They now
follow sam's lineaddr (editors/sam/address.c:180-215): #0,#0 and #2,#2.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/addr.zig')
| -rw-r--r-- | src/ninep/addr.zig | 81 |
1 files changed, 61 insertions, 20 deletions
diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig index 1e47183b..e1d6da0f 100644 --- a/src/ninep/addr.zig +++ b/src/ninep/addr.zig @@ -188,30 +188,43 @@ pub const Addr = struct { while (q0 > 0 and a.text[q0 - 1] != '\n') q0 -= 1; return .{ .q0 = clip(q0), .q1 = clip(q1) }; }, - '+' => { - if (q1 > 0) while (q1 < a.text.len and a.text[q1 - 1] != '\n') { - q1 += 1; - }; - q0 = q1; - }, - else => { - q0 = 0; - q1 = 0; - }, + else => {}, } - while (line > 0 and q1 < a.text.len) { - const ch = a.text[q1]; - q1 += 1; - if (ch == '\n' or q1 == a.text.len) { - line -= 1; - if (line > 0) q0 = q1; + // Forward, sam's lineaddr (editors/sam/address.c:180-215), not acme's + // number(): line n is found by counting newlines, and a line just + // past the last newline is an empty one there, so `1` of an empty + // text is #0,#0 and `2` of "a\n" is #2,#2; only past that is it out + // of range. + const nc = a.text.len; + var p: usize = 0; + var p1: usize = 0; + if (n == 0) { + if (dir == 0 or q1 == 0) return .{ .q0 = 0, .q1 = 0 }; + p1 = q1; + p = q1 - 1; + } else { + var k: u32 = 1; + if (dir != 0 and q1 > 0) { + p = q1 - 1; + k = @intFromBool(a.text[p] == '\n'); + p += 1; + } + while (k < n) { + if (p >= nc) { + a.err = e_range; + return null; + } + if (a.text[p] == '\n') k += 1; + p += 1; } + p1 = p; } - if (line > 0) { - a.err = e_range; - return null; + while (p < nc) { + const ch = a.text[p]; + p += 1; + if (ch == '\n') break; } - return .{ .q0 = clip(q0), .q1 = clip(q1) }; + return .{ .q0 = clip(p1), .q1 = clip(p) }; } /// acme's regexp(): forward from the end of `r` to the limit, wrapping @@ -476,3 +489,31 @@ test "the address language, form by form" { _ = th.wr(p, addr, "#0"); try testing.expectEqual(E.INVAL, th.wr(p, addr, nested).errno()); } + +test "a line just past the last newline is an empty one there, as sam's lineaddr has it" { + const Case = struct { text: []const u8, expr: []const u8, q0: u32, q1: u32 }; + for ([_]Case{ + .{ .text = "", .expr = "1", .q0 = 0, .q1 = 0 }, + .{ .text = "a\n", .expr = "2", .q0 = 2, .q1 = 2 }, + .{ .text = "a\nb", .expr = "2", .q0 = 2, .q1 = 3 }, + .{ .text = "a\nb\n", .expr = "1", .q0 = 0, .q1 = 2 }, + .{ .text = "a\nb\n", .expr = "0", .q0 = 0, .q1 = 0 }, + .{ .text = "a\nb\n", .expr = "1+1", .q0 = 2, .q1 = 4 }, + .{ .text = "a\nb\n", .expr = "$", .q0 = 4, .q1 = 4 }, + }) |c| { + var a: Addr = .{ .text = c.text, .lim = null, .expr = c.expr }; + const r = a.address(.{ .q0 = 0, .q1 = 0 }).?; + try testing.expectEqual(c.q0, r.q0); + try testing.expectEqual(c.q1, r.q1); + } + for ([_][2][]const u8{ .{ "", "2" }, .{ "a\n", "3" } }) |c| { + var a: Addr = .{ .text = c[0], .lim = null, .expr = c[1] }; + try testing.expectEqual(null, a.address(.{ .q0 = 0, .q1 = 0 })); + try testing.expectEqualStrings(e_range, a.err); + } + // Edit's addresses are these: 1i on an empty file inserts. + const p = try th.withFile(testing.allocator, ""); + defer p.deinit(); + try testing.expectEqual(Status.ok, th.wr(p, Node.of(th.serialOf(p), .ctl), "Edit 1i/header/\n").reply.status); + try testing.expectEqualStrings("header", p.panes[0].?.file.?.content); +} |
