summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 23:07:55 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit6398e62741815ff11fa0c6380d0e7cde0c294fb9 (patch)
tree24d4cf5b261c750751d330dfa7b9dbddaf666e4a /src
parentd5054831d48c8cf400383ad8d842462bf2c203f8 (diff)
downloadpardes-6398e62741815ff11fa0c6380d0e7cde0c294fb9.tar.gz
pardes-6398e62741815ff11fa0c6380d0e7cde0c294fb9.zip
A look at file:0:0 is refused, as a zero column after any other line is
The zero-column check ran only after a nonzero line, so file:0:0 went on as an address and failed some other way, or not at all. The check now reads the look's whole tail first, line 0 included, and refuses a zero column in addr's words. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/look.zig23
1 files changed, 22 insertions, 1 deletions
diff --git a/src/look.zig b/src/look.zig
index 17ec8a1c..238e479f 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1262,6 +1262,24 @@ fn zeroColumn(rest: []const u8) bool {
return false;
}
+/// Whether `text` ends in a `:line:col` place (each spelling zeroColumn
+/// knows) whose column is 0, line 0 included.
+fn zeroColumnTail(text: []const u8) bool {
+ var i: usize = 0;
+ while (std.mem.indexOfScalarPos(u8, text, i, ':')) |at| : (i = at + 1) {
+ const rest = text[at..];
+ for (rest[1..]) |c| if (std.mem.indexOfScalar(u8, "0123456789:-", c) == null) break else {
+ if (rest.len > 1 and std.ascii.isDigit(rest[1]) and zeroColumn(rest)) return true;
+ };
+ }
+ return false;
+}
+
+test "a zero column ends a look's text, line 0 included" {
+ for ([_][]const u8{ "f.txt:0:0", "f.txt:3:0", "dir/f.txt:3:2-0" }) |t| try std.testing.expect(zeroColumnTail(t));
+ for ([_][]const u8{ "f.txt:3", "f.txt:3:1", "f.txt:0", "f.txt", "a:b:0x" }) |t| try std.testing.expect(!zeroColumnTail(t));
+}
+
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));
@@ -1645,6 +1663,10 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
const trimmed = std.mem.trim(u8, txt, " \t\r\n");
// acme's `file:addr` and `:addr` (look.c:450-543): an address after
// the colon, evaluated in that file from its dot (openfile, look.c:675).
+ // `file:3:0`, and `file:0:0` too: columns count from 1, as addr's do,
+ // and a zero is refused in addr's words rather than taken as no column
+ // (or, after line 0, as an address).
+ if (zeroColumnTail(trimmed)) return p.reportFailure(id, @import("ninep/addr.zig").e_col_zero);
if (addressed(trimmed)) |ad| return lookAddress(p, id, ad.path, ad.expr);
// `:N` on a PDF is its page N, never a search for the text.
if (comptime pdf_enabled) if (pane.hasPdf() and trimmed.len > 1 and trimmed[0] == ':')
@@ -1652,7 +1674,6 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
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;