summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ninep/addr.zig81
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);
+}