summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 13:59:25 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit9ca008a27c4ab83a29c1798f4eb081eef372624c (patch)
tree53723e46dd928d566ffa6b625911f755d53d83c0
parente8fc2964acdf7156ab0c9961b7bf83d74f152192 (diff)
downloadpardes-9ca008a27c4ab83a29c1798f4eb081eef372624c.tar.gz
pardes-9ca008a27c4ab83a29c1798f4eb081eef372624c.zip
A look at file:3:0 is refused as addr 3:0 is: a column counts from 1
addr refuses a column 0 with EINVAL, "address out of range: a column counts from 1". A look took `file:3:0` as line 3 with no column, since the parser holds no column as 0, and succeeded. Now a look whose column, or end column, is written as 0 is refused with addr's words and errno. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/look.zig22
-rw-r--r--src/ninep/ctl.zig15
2 files changed, 36 insertions, 1 deletions
diff --git a/src/look.zig b/src/look.zig
index 36eae75c..de51804d 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1233,6 +1233,25 @@ fn missedLine(p: *Pardes, id: usize, path: []const u8, line: anytype) void {
missed(p, id, "{s} has no line {d}", .{ missText(path, &cut), line });
}
+/// Whether `:line:col`, `:line:col-col` or `:line:col-line:col` after a
+/// path names a column 0, which parsePathLine reads as no column.
+fn zeroColumn(rest: []const u8) bool {
+ var fields = std.mem.splitScalar(u8, rest, ':');
+ _ = fields.next(); // before the first ':'
+ _ = fields.next() orelse return false; // the line
+ const col = fields.next() orelse return false;
+ var ends = std.mem.splitScalar(u8, col, '-');
+ if (std.mem.eql(u8, ends.next().?, "0")) return true;
+ if (ends.next()) |end| if (std.mem.eql(u8, end, "0")) return true;
+ if (fields.next()) |end_col| if (std.mem.eql(u8, end_col, "0")) return true;
+ return false;
+}
+
+test "a zero column after a path is found, in every spelling a look takes" {
+ for ([_][]const u8{ ":3:0", ":3:0-4", ":3:2-0", ":3:2-5:0" }) |rest| try std.testing.expect(zeroColumn(rest));
+ for ([_][]const u8{ "", ":3", ":3:1", ":3:2-5", ":3:2-5:7", ":30:10" }) |rest| try std.testing.expect(!zeroColumn(rest));
+}
+
fn missed(p: *Pardes, id: usize, comptime fmt: []const u8, args: anytype) void {
p.look_missed = true;
var said: [miss_path_cap + 64]u8 = undefined;
@@ -1504,6 +1523,9 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
// the colon, evaluated in that file from its dot (openfile, look.c:675).
if (addressed(trimmed)) |ad| return lookAddress(p, id, ad.path, ad.expr);
const pl = parsePathLine(trimmed);
+ // `file:3:0`: columns count from 1, as addr's do, and a zero is refused
+ // in addr's words rather than taken as no column.
+ if (pl.at.line != 0 and zeroColumn(trimmed[pl.path.len..])) return p.reportFailure(id, @import("ninep/addr.zig").e_col_zero);
if (comptime pdf_enabled) if (pane.hasPdf()) clearNavigationSelection(p, pane);
if (comptime pdf_enabled) if (panes.Pdf.lookSection(p, id, pl.path, pl.at)) return;
var realbuf: [4096]u8 = undefined;
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index cf0b241c..4312653c 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -289,7 +289,8 @@ fn failureErrno(failure: []const u8) u16 {
if (std.mem.indexOf(u8, failure, "no such") != null or std.mem.indexOf(u8, failure, "not found") != null) return E.NOENT;
// A pattern refused (Edit's too) is malformed input, as 9ns reads it;
// so is a word this build has not (`invalid: acme's Put ...`).
- if (std.mem.indexOf(u8, failure, "bad regular expression") != null or std.mem.startsWith(u8, failure, "invalid")) return E.INVAL;
+ if (std.mem.indexOf(u8, failure, "bad regular expression") != null or std.mem.startsWith(u8, failure, "invalid") or
+ std.mem.startsWith(u8, failure, "address out of range")) return E.INVAL;
return E.IO;
}
@@ -2504,6 +2505,18 @@ test "with no pane at all, the root look opens its file and the root exec runs i
try testing.expectEqualStrings(session, made.cwdSlice());
}
+test "a look at file:3:0 is refused as addr 3:0 is, a column counting from 1" {
+ const p = try withFile(testing.allocator, "one\ntwo\nthree\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const by_addr = wr(p, Node.of(serial, .addr), "3:0");
+ const by_look = wr(p, Node.of(serial, .look), "/test.txt:3:0\n");
+ try testing.expectEqual(E.INVAL, by_addr.errno());
+ try testing.expectEqual(E.INVAL, by_look.errno());
+ try testing.expectEqualStrings(by_addr.reply.ename, by_look.reply.ename);
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .look), "/test.txt:3:1\n").reply.status);
+}
+
test "a line starting with # runs as nothing at an exec or a ctl, silently" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();