summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 02:44:38 -0300
committerGabriel Schneider <[email protected]>2026-10-01 02:44:38 -0300
commit43b75a3c79e204b758df8d361c213364dc4fe064 (patch)
tree6c3b88a5824b573acc53a1f336138681f7c46bf9 /src
parent3bddd662eac9a7829174f3c54b498bda84df1961 (diff)
downloadpardes-43b75a3c79e204b758df8d361c213364dc4fe064.tar.gz
pardes-43b75a3c79e204b758df8d361c213364dc4fe064.zip
A look line's trailing colon is dropped, as a click leaves it off, and a look whose address fails never leaves the file it opened
`a.txt:2:3:`, as a compiler prints a place, failed as bad address syntax, and still opened a.txt: the file was opened to read the address in, and stayed when the address failed. The trailing colon is now no part of the place, and a pane opened only to evaluate an address that fails goes again, the miss said from the pane looked from. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/look.zig20
-rw-r--r--src/ninep/ctl.zig30
2 files changed, 48 insertions, 2 deletions
diff --git a/src/look.zig b/src/look.zig
index 105b7717..f208c1fb 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1455,11 +1455,17 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void {
// A miss names the file it was looked for in, and is that file's pane's.
var cut: [miss_path_cap + "…".len]u8 = undefined;
const name = missText(f.path, &cut);
+ // A look whose address fails opens nothing: a pane it opened to read
+ // the address in goes again, the miss said from the pane it came from.
+ const opened_here = open == null and target != id;
const r = a.address(ninep_pane.dotOf(pane)) orelse {
+ if (opened_here) return closeAndMiss(p, id, target, name, a.err);
return missed(p, target, "{s}: {s}", .{ name, a.err });
};
if (a.i < expr.len or r.q0 > r.q1) {
- return missed(p, target, "{s}: {s}", .{ name, if (r.q0 > r.q1) "addresses out of order" else "bad address syntax" });
+ const why = if (r.q0 > r.q1) "addresses out of order" else "bad address syntax";
+ if (opened_here) return closeAndMiss(p, id, target, name, why);
+ return missed(p, target, "{s}: {s}", .{ name, why });
}
clearNavigationSelection(p, pane);
ninep_pane.setDot(pane, r);
@@ -1481,6 +1487,14 @@ fn lookPdfPage(p: *Pardes, id: usize, target: usize, pv: anytype, expr: []const
p.active = target;
}
+fn closeAndMiss(p: *Pardes, id: usize, opened: usize, name: []const u8, why: []const u8) void {
+ var kept: [miss_path_cap + "…".len]u8 = undefined;
+ @memcpy(kept[0..name.len], name);
+ p.removePane(opened, null) catch {};
+ if (p.panes[id] != null) p.active = id;
+ missed(p, id, "{s}: {s}", .{ kept[0..name.len], why });
+}
+
/// `@p<terminal>:<address>`: any address over the terminal's body as its
/// logical lines read (the lines a +Search lists as `@p<serial>:<line>`),
/// from its cursor; the match is selected there, as a +Search row's is.
@@ -1672,7 +1686,9 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
};
// The operand may borrow selected terminal/PDF text released below.
const txt = p.scratch.allocator().dupe(u8, looked) catch return;
- const trimmed = std.mem.trim(u8, txt, " \t\r\n");
+ // A trailing `:` (`a.txt:2:3:`, as a compiler's message ends) is no
+ // part of the place, as a click's expansion leaves it off.
+ const trimmed = std.mem.trimEnd(u8, 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,
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 6326b1c0..9092a307 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1852,6 +1852,36 @@ test "a bad setting value is quoted, so two different bad values log as two reco
try testing.expect(!th.logHas(p, "(x2)"));
}
+test "a look line's trailing colon is dropped, and a look whose address fails never leaves its file open" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "a.txt", .data = "one\ntwo\nthree\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var line: [4200]u8 = undefined;
+ const count = struct {
+ fn of(pp: *Pardes) usize {
+ var n: usize = 0;
+ for (pp.panes) |slot| n += @intFromBool(slot != null);
+ return n;
+ }
+ }.of;
+ const before = count(p);
+ // Failing addresses: nothing opened, the write still answered.
+ inline for ([_][]const u8{ "{s}/a.txt:/nomatch/\n", "{s}/a.txt:9:\n" }) |fmt| {
+ _ = wr(p, root_look, try std.fmt.bufPrint(&line, fmt, .{dir}));
+ try testing.expectEqual(before, count(p));
+ }
+ // `a.txt:2:3:` is `a.txt:2:3`: it opens there.
+ try testing.expectEqual(Status.ok, wr(p, root_look, try std.fmt.bufPrint(&line, "{s}/a.txt:2:3:\n", .{dir})).reply.status);
+ try testing.expectEqual(before + 1, count(p));
+ const opened = p.panes[p.active].?;
+ try testing.expect(std.mem.endsWith(u8, opened.file.?.path, "/a.txt"));
+ try testing.expectEqual(@as(i32, 1), opened.body.cur_row);
+}
+
test "a bare :N or :N:M look addresses the pane itself, from its look, the root's and event write-back" {
const p = try withFile(testing.allocator, "alpha\nbeta\ngamma\n");
defer p.deinit();