summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 01:45:24 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commiteeac8cdf456148f02546f69dc197c6e9dca7876d (patch)
treeaa3ffac5b2687e2d300c7e8944a45aaeab36c545
parent8322d6507a854fc41bba42b9e8b8672d33cfe18b (diff)
downloadpardes-eeac8cdf456148f02546f69dc197c6e9dca7876d.tar.gz
pardes-eeac8cdf456148f02546f69dc197c6e9dca7876d.zip
A look's miss repeats the path whole, up to 256 bytes
The miss message clipped what was looked at to 48 bytes, mid-word, and a path past 160 bytes turned the has-no-line message into a bare no match. Both now carry up to 256 bytes, cut on a character boundary with an ellipsis past that. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/look.zig35
-rw-r--r--src/ninep/ctl.zig14
2 files changed, 42 insertions, 7 deletions
diff --git a/src/look.zig b/src/look.zig
index 9dfd912f..5d60e8b3 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1181,14 +1181,32 @@ pub fn followPdfLink(p: *Pardes, id: usize, probe: *const panes.Pdf.WordProbe) v
/// A look that found nothing: said on the message row, logged as an `err`
/// record, and marked so that /look reads back nothing (ninep/ctl.zig run).
+/// The most of a looked-at path or text a miss repeats; past it, cut on a
+/// character boundary and ended with `…`.
+const miss_path_cap = 256;
+
+fn missText(text: []const u8, buf: *[miss_path_cap + "…".len]u8) []const u8 {
+ if (text.len <= miss_path_cap) return text;
+ var n: usize = miss_path_cap;
+ while (n > 0 and text[n] & 0xC0 == 0x80) n -= 1;
+ @memcpy(buf[0..n], text[0..n]);
+ @memcpy(buf[n..][0.."…".len], "…");
+ return buf[0 .. n + "…".len];
+}
+
+fn missedLine(p: *Pardes, id: usize, path: []const u8, line: anytype) void {
+ var cut: [miss_path_cap + "…".len]u8 = undefined;
+ missed(p, id, "{s} has no line {d}", .{ missText(path, &cut), line });
+}
+
fn missed(p: *Pardes, id: usize, comptime fmt: []const u8, args: anytype) void {
p.look_missed = true;
- var said: [160]u8 = undefined;
+ var said: [miss_path_cap + 64]u8 = undefined;
const why = std.fmt.bufPrint(&said, fmt, args) catch "no match";
- var msg: [176]u8 = undefined;
+ var msg: [said.len + 8]u8 = undefined;
p.setMessage(id, std.fmt.bufPrint(&msg, "Look: {s}", .{why}) catch "Look: no match");
const serial = if (p.panes[id]) |pane| pane.serial else 0;
- var rec: [200]u8 = undefined;
+ var rec: [said.len + 32]u8 = undefined;
pardes.ctlfs.events.notePath(p, "err", std.fmt.bufPrint(&rec, "{d} look: {s}", .{ serial, why }) catch return);
}
@@ -1201,7 +1219,7 @@ fn settleLine(p: *Pardes, id: usize, path: []const u8, at: Spot, before: usize,
if (at.line > panes.File.nlines(p.gpa, f)) {
if (opened) |free| p.removePane(free, null) catch {};
if (p.panes[before] != null) p.active = before;
- return missed(p, id, "{s} has no line {d}", .{ path, at.line });
+ return missedLine(p, id, path, at.line);
}
if (at.line > 0 and at.col == 0 and at.end_line == 0) {
const start = modal.lineStartOffset(f.content, at.line - 1);
@@ -1290,7 +1308,7 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
// A line past the end of an open pane is a miss (the target's line is
// already clamped, so the one written is asked).
if (found == .pane and found.pane.id < MAX_PANES) if (p.panes[found.pane.id]) |shown| if (shown.file) |*f| if (pl.at.line > panes.File.nlines(p.gpa, f))
- return missed(p, id, "{s} has no line {d}", .{ trimmed, pl.at.line });
+ return missedLine(p, id, trimmed, pl.at.line);
// A miss changes nothing, the selection included; anything else starts
// from a clean one.
if (found != .none) clearNavigationSelection(p, pane);
@@ -1301,7 +1319,10 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
p.reportError(id, "search", err);
return;
};
- if (rows == 0) return missed(p, id, "no match for \"{s}\"", .{@import("Messages.zig").clip(trimmed, 48)});
+ if (rows == 0) {
+ var cut: [miss_path_cap + "…".len]u8 = undefined;
+ return missed(p, id, "no match for \"{s}\"", .{missText(trimmed, &cut)});
+ }
clearNavigationSelection(p, pane);
const at = pane.search_row;
_ = searchStep(p, id, 1);
@@ -1347,7 +1368,7 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
// touches nothing.
if (openPaneTarget(p, target.path, target.at)) |open| if (p.panes[open.pane.id].?.file) |*f|
if (target.at.line > panes.File.nlines(p.gpa, f))
- return missed(p, id, "{s} has no line {d}", .{ target.path, target.at.line });
+ return missedLine(p, id, target.path, target.at.line);
// focus an existing pane on this path (rescrolled), else open
const before = p.active;
var opened: ?usize = null;
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index caa3f009..65878feb 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -997,6 +997,20 @@ test "the root look's file:/re/ goes on from the dot, and a miss leaves the dot
try testing.expectEqualStrings(" 0 3 ", rd(p, dot, 0, 64).bytes);
}
+test "a look's miss repeats the path whole up to 256 bytes, then cuts it with an ellipsis" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "x\n");
+ defer p.deinit();
+ const root = @intFromEnum(tree.TopFile.look);
+ const long = "some/deeply/nested/directory/that/goes/on/and/on/with/a_rather_long_file_name.txt";
+ var line: [512]u8 = undefined;
+ _ = wr(p, root, try std.fmt.bufPrint(&line, "{s}\n", .{long}));
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&line, "look: no match for \"{s}\"\n", .{long})));
+ const huge = "é" ** 150; // 300 bytes: cut at 256, between characters
+ _ = wr(p, root, huge ++ "\n");
+ try testing.expect(th.logHas(p, "look: no match for \"" ++ "é" ** 128 ++ "…\"\n"));
+}
+
test "exec runs a builtin at the pane and records the pane it acted on" {
const gpa = testing.allocator;
const p = try withFile(gpa, "Msg fs-ran\n");