summaryrefslogtreecommitdiff
path: root/src/ninep/addr.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/addr.zig')
-rw-r--r--src/ninep/addr.zig56
1 files changed, 51 insertions, 5 deletions
diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig
index 0537c17a..1e47183b 100644
--- a/src/ninep/addr.zig
+++ b/src/ninep/addr.zig
@@ -91,6 +91,26 @@ pub const Addr = struct {
if (d < '0' or d > '9') break;
n = @min(n * 10 + (d - '0'), std.math.maxInt(u32));
}
+ // pardes's `line:col`, Look's `file:12:5` spelling (look.zig
+ // parsePathLine): the point at that column of that line, the
+ // column in bytes from 1, clamped to the line's end and snapped
+ // to a character's start as Look's cursor is. Not sam's.
+ if (size == .line and dir == 0 and 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;
+ var col: u64 = 0;
+ while (a.i < a.expr.len and a.expr[a.i] >= '0' and a.expr[a.i] <= '9') : (a.i += 1)
+ col = @min(col * 10 + (a.expr[a.i] - '0'), std.math.maxInt(u32));
+ const lines = modal.cursorLineCount(a.text);
+ if (n == 0 or n > lines or col == 0) {
+ a.err = e_range;
+ return null;
+ }
+ const q = clip(modal.offsetAt(a.text, .{ .row = @intCast(n - 1), .col = @intCast(col - 1) }));
+ r = .{ .q0 = q, .q1 = q };
+ continue;
+ }
r = a.number(r, @intCast(n), dir, size) orelse return null;
dir = 0;
size = .line;
@@ -202,8 +222,8 @@ pub const Addr = struct {
a.err = "no previous regular expression";
return null;
}
- var rx = regexp_.Regex.compile(pat) catch {
- a.err = e_regexp;
+ var rx = regexp_.Regex.compile(pat) catch |err| {
+ a.err = if (err == error.Anchor) regexp_.Regex.e_anchor else e_regexp;
return null;
};
const found = if (back) found: {
@@ -340,13 +360,34 @@ test "a failed address leaves none, so data refuses rather than act at the last
try testing.expectEqualStrings(pane_files.e_addr_failed, th.rd(p, addr, 0, 64).reply.ename);
try testing.expectEqual(E.INVAL, th.call(p, .{ .tag = 1, .op = .setattr, .node = Node.of(serial, .xdata), .truncate = true }).errno());
try testing.expectEqualStrings("one\ntwo\n", p.panes[0].?.file.?.content);
- // A good address, or a truncated addr, gives it one again.
+ // A good address gives it one again; truncating addr does not.
_ = th.wr(p, addr, "#0,#3");
try testing.expectEqual(Status.ok, th.wr(p, data, "ONE").reply.status);
try testing.expectEqualStrings("ONE\ntwo\n", p.panes[0].?.file.?.content);
_ = th.wr(p, addr, "99");
_ = th.call(p, .{ .tag = 1, .op = .setattr, .node = addr, .truncate = true });
- try testing.expectEqual(Status.ok, th.wr(p, data, "0").reply.status);
+ try testing.expectEqualStrings(pane_files.e_addr_failed, th.wr(p, data, "0").reply.ename);
+}
+
+test "a shell's > on addr searches on from the last address, and 0 starts over" {
+ const p = try th.withFile(testing.allocator, "foo x foo y foo\n");
+ defer p.deinit();
+ const serial = th.serialOf(p);
+ const addr = Node.of(serial, .addr);
+ const data = Node.of(serial, .data);
+ // echo /foo/ > addr; printf foobar > data -- twice over, as a loop does.
+ for (0..3) |_| {
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = addr, .truncate = true });
+ try testing.expectEqual(Status.ok, th.wr(p, addr, "/foo/").reply.status);
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = data, .truncate = true });
+ try testing.expectEqual(Status.ok, th.wr(p, data, "foobar").reply.status);
+ }
+ try testing.expectEqualStrings("foobar x foobar y foobar\n", p.panes[0].?.file.?.content);
+ // .+#1 moves on from where the last write left the address.
+ _ = th.wr(p, addr, "0");
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = addr, .truncate = true });
+ try testing.expectEqual(Status.ok, th.wr(p, addr, ".+#1").reply.status);
+ try testing.expectEqualStrings(" 1 1 ", th.rd(p, addr, 0, 64).bytes);
}
test "the address language, form by form" {
@@ -372,6 +413,11 @@ test "the address language, form by form" {
.{ .expr = "/two/", .q0 = 4, .q1 = 7 },
.{ .expr = "/t.o/", .q0 = 4, .q1 = 7 },
.{ .expr = "1\n", .q0 = 0, .q1 = 4 },
+ // pardes's line:col, composing like any simple address.
+ .{ .expr = "2:2", .q0 = 5, .q1 = 5 },
+ .{ .expr = "2:99", .q0 = 7, .q1 = 7 },
+ .{ .expr = "2:2,3:1", .q0 = 5, .q1 = 8 },
+ .{ .expr = "2:2+#3", .q0 = 8, .q1 = 8 },
}) |c| {
_ = th.wr(p, addr, "#0");
const w = th.wr(p, addr, c.expr);
@@ -420,7 +466,7 @@ test "the address language, form by form" {
for ([_][2][]const u8{
.{ "zzz", e_syntax }, .{ "/nomatch/", e_no_match }, .{ "99", e_range },
.{ "#999", e_range }, .{ "/a[/", e_regexp }, .{ "/(a/", e_regexp },
- .{ "/*a/", e_regexp },
+ .{ "/*a/", e_regexp }, .{ "99:1", e_range }, .{ "2:0", e_range },
}) |c| {
_ = th.wr(p, addr, "#0");
try testing.expectEqualStrings(c[1], th.wr(p, addr, c[0]).reply.ename);