From 6122159572f1cc950666df484e2074cf32f22eb5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 16:51:34 -0300 Subject: A 9P look keeps its leading blanks: an indented line, or blanks alone, is looked for as written Every line written to look or exec went through Messages.next, which trimmed blanks from both ends. A look of ` indented` then found the first bare `indented`, and a diff's blank context line ` ` looked at nothing. A look's line is now its text: only its `\r` and newline go. lookAt keeps the blanks for the word search too; it expands `~` only for a look that starts with one, and the path, address and command readings trim for themselves as before. runSearch keeps a pattern's blanks, since a builtin's argument comes trimmed already. An exec line is trimmed as before. fs.md says so. Co-Authored-By: Claude Opus 5.5 --- src/look.zig | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'src/look.zig') diff --git a/src/look.zig b/src/look.zig index 76b58fbe..0f99722d 100644 --- a/src/look.zig +++ b/src/look.zig @@ -580,8 +580,10 @@ fn lookFirstHit(p: *Pardes, id: usize) void { /// `open_empty` false a text search that found nothing opens no buffer. pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start: SearchStart, open_empty: bool) !usize { const pane = p.panes[id] orelse return 0; - const pat = std.mem.trim(u8, pat_raw, " \t\r\n"); - if (pat.len == 0) return 0; + // Blanks at either end are the pattern's (a look of an indented line); + // a builtin's argument comes trimmed already. + const pat = std.mem.trimEnd(u8, pat_raw, "\r\n"); + if (std.mem.trim(u8, pat, " \t").len == 0) return 0; const arena = p.scratch.allocator(); // where the pane lives: a file's directory, a shell's cwd — the walk // root, and the directory the results buffer is named in. From a @@ -1325,7 +1327,9 @@ fn addressed(tok: []const u8) ?struct { path: []const u8, expr: []const u8 } { /// A look at text that names nothing to open: a `./` name is a missing /// file, anything else a search for the text as written. -fn lookUnresolved(p: *Pardes, id: usize, pane: *Pane, trimmed: []const u8) void { +/// `text` is what a word search looks for: the look as written, its blanks +/// kept; `trimmed` what a name is read from. +fn lookUnresolved(p: *Pardes, id: usize, pane: *Pane, trimmed: []const u8, text: []const u8) void { // A `./` or `../` name opened nothing: said, ENOENT, not searched // for as a word. What is missing is the pane's directory when // that is gone, else the file named. @@ -1340,8 +1344,9 @@ fn lookUnresolved(p: *Pardes, id: usize, pane: *Pane, trimmed: []const u8) void // A raw terminal's clicks are its program's; a look written over // 9P is no keyboard input, and searches whatever the key mode. if (pane.image != null or (pane.body.mode == .tty and !p.fs.serving)) return; - if (p.settings.look_word == .search and pane.file != null) return lookWord(p, id, pane, trimmed); - const rows = runSearch(p, id, trimmed, .text, .cursor, false) catch |err| { + if (text.len == 0) return; + if (p.settings.look_word == .search and pane.file != null) return lookWord(p, id, pane, text); + const rows = runSearch(p, id, text, .text, .cursor, false) catch |err| { p.reportError(id, "search", err); return; }; @@ -1380,7 +1385,7 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void { // A path that names nothing to open: the whole of what was written // is looked for, as `zzq:2` is, and a miss quotes it as written. if (!by_serial and resolveLookTarget(p, id, path, &realbuf) == .none) - return lookUnresolved(p, id, p.panes[id] orelse return, path.ptr[0 .. path.len + 1 + expr.len]); + return lookUnresolved(p, id, p.panes[id] orelse return, path.ptr[0 .. path.len + 1 + expr.len], path.ptr[0 .. path.len + 1 + expr.len]); const before = p.active; const failures = p.fs.failures; const was_missed = p.look_missed; @@ -1598,8 +1603,12 @@ fn diffFile(base: []const u8, path: []const u8, out: *[4096]u8) ?[]const u8 { pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void { const pane = p.panes[id] orelse return; // `~/x`, `~/x:12`, `~/x:/re/`: home, before anything reads the text. + // Anything else as written: a look of ` return x` is of those blanks + // too (the path, address and command readings below trim for + // themselves). var home_buf: [4096]u8 = undefined; - const operand = filesystem.expandHome(std.mem.trim(u8, operand_raw, " \t\r\n"), &home_buf); + const stripped = std.mem.trim(u8, operand_raw, " \t\r\n"); + const operand = if (stripped.len > 0 and stripped[0] == '~') filesystem.expandHome(stripped, &home_buf) else std.mem.trimEnd(u8, operand_raw, "\r\n"); // A +Recent row is a remembered place: reopened and clamped to the file // as it is now, never a miss for a line the file no longer has. if (isRecentList(pane)) { @@ -1665,7 +1674,7 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void { pane.sel = @splat(.{}); } switch (found) { - .none => lookUnresolved(p, id, pane, trimmed), + .none => lookUnresolved(p, id, pane, trimmed, std.mem.trimEnd(u8, txt, "\r\n")), .pane => |t| { // `@p` names no open pane: a miss, said. if (t.id >= MAX_PANES or p.panes[t.id] == null) return missed(p, id, "no pane {s}", .{pl.path[0..@min(pl.path.len, 32)]}); -- cgit v1.3