From e0a2fd56777cf0f571125650ec74e75c3f6a8c44 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 09:00:11 -0300 Subject: Grep and Find count the rows they found, so a hit opens its +Search and a miss fails Round 18 made a search that found nothing fail, but grep and find never counted their rows (only a pane search did), so every Grep read as a miss. Both count the lines they wrote now; a Find or Grep that finds nothing fails alike, `find: no match, not found` / `grep: ...`, ENOENT, and opens no empty +Search. Co-Authored-By: Claude Opus 5.5 --- src/builtins.zig | 10 ++++++++-- src/look.zig | 4 +++- src/ninep/ctl.zig | 22 ++++++++++++++++++---- 3 files changed, 29 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/builtins.zig b/src/builtins.zig index 756eeae7..b97f68f8 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -1322,6 +1322,11 @@ pub const Mini = struct { // ---- search ---- +/// A Find or Grep that finds nothing fails as one, in words 9ns reads as +/// ENOENT (`not found`), the same for both. +const e_search_miss_find = "find: no match, not found"; +const e_search_miss_grep = "grep: no match, not found"; + /// List the files below this pane's directory whose names hold its argument, in a +Search. pub const Find = struct { pub const takes_arg = true; @@ -1330,7 +1335,8 @@ pub const Find = struct { pub fn run(c: Ctx) void { const pat = std.mem.trim(u8, c.arg orelse "", " \t\r\n"); if (pat.len > 0) { - _ = look.runSearch(c.p, c.id, pat, .find, .top, true) catch |err| c.p.reportError(c.id, "find", err); + const rows = look.runSearch(c.p, c.id, pat, .find, .top, false) catch |err| return c.p.reportError(c.id, "find", err); + if (rows == 0) c.p.reportFailure(c.id, e_search_miss_find); return; } c.p.startPrompt(c.pane, &c.pane.body, .{ .search = config.find_marker }); @@ -1347,7 +1353,7 @@ pub const Grep = struct { const pat = std.mem.trim(u8, c.arg orelse "", " \t\r\n"); if (pat.len > 0) { const rows = look.runSearch(c.p, c.id, pat, .grep, .top, false) catch |err| return c.p.reportError(c.id, "grep", err); - if (rows == 0) c.p.reportFailure(c.id, "grep: no match"); + if (rows == 0) c.p.reportFailure(c.id, e_search_miss_grep); return; } c.p.startPrompt(c.pane, &c.pane.body, .{ .search = config.grep_marker }); diff --git a/src/look.zig b/src/look.zig index ba022bef..fa86aee4 100644 --- a/src/look.zig +++ b/src/look.zig @@ -620,8 +620,10 @@ pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start error.FileNotFound, error.NotDir, error.AccessDenied => continue, else => return err, }; + nrows = std.mem.count(u8, out[0..out_len], "\n"); } else if (kind == .find) { out_len = try filesystem.find(arena, dir, pat, out); + nrows = std.mem.count(u8, out[0..out_len], "\n"); } else if (pane.hasPdf()) { if (comptime pdf_enabled) { const found = try panes.Pdf.searchRows( @@ -661,7 +663,7 @@ pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start nrows += 1; } } - if (!open_empty and (kind == .text or kind == .grep) and nrows == 0) return 0; + if (!open_empty and nrows == 0) return 0; const content = try p.gpa.dupe(u8, out[0..out_len]); // the buffer records WHICH search filled it, pattern and all: Find and // Grep are builtins (words you can execute), the bare `/` is a key diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 30091ae1..d1f920a2 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -243,9 +243,10 @@ pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, i } /// The errno a builtin's failure answers with: ENOENT for what is not -/// there (`no such`, the words 9ns reads as ENOENT too), else EIO. +/// there (`no such`, `not found`, the words 9ns reads as ENOENT too), else +/// EIO. fn failureErrno(failure: []const u8) u16 { - return if (std.mem.indexOf(u8, failure, "no such") != null) E.NOENT else E.IO; + return if (std.mem.indexOf(u8, failure, "no such") != null or std.mem.indexOf(u8, failure, "not found") != null) E.NOENT else E.IO; } pub fn resultsLen(p: *Pardes) u64 { @@ -1831,18 +1832,31 @@ test "Grep with no hit says grep: no match and opens nothing, a missing pane dir var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.writeFile(testing.io, .{ .sub_path = "a.txt", .data = "alpha\n" }); + try tmp.dir.writeFile(testing.io, .{ .sub_path = "other.txt", .data = "one\nbravo here\nthree bravo\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; _ = wr(p, Node.of(serialOf(p), .name), try std.fmt.bufPrint(&line, "{s}/a.txt\n", .{dir})); + // A hit opens its +Search, a row a line: exec reads it back. + try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .exec), "Grep bravo\n").reply.status); + const grepped = p.panes[p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, root_exec, 0, 64).bytes, "\n"), 10)).?].?; + try testing.expectEqual(@as(usize, 2), std.mem.count(u8, grepped.file.?.content, "other.txt:")); + try testing.expect(std.mem.indexOf(u8, grepped.file.?.content, "other.txt:2") != null); + try testing.expect(std.mem.indexOf(u8, grepped.file.?.content, "other.txt:3") != null); + try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .exec), "Find other\n").reply.status); + const found = p.panes[p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, root_exec, 0, 64).bytes, "\n"), 10)).?].?; + try testing.expect(std.mem.indexOf(u8, found.file.?.content, "other.txt") != null); // A second pane in a directory that is not there. const other = try th.newPane(p); _ = wr(p, Node.of(other, .name), "/tmp/pardes-no-such-dir-zz/b.txt\n"); var count: usize = 0; for (p.panes) |slot| count += @intFromBool(slot != null); const missed = wr(p, Node.of(serialOf(p), .exec), "Grep zzqqnomatchzz\n"); - try testing.expectEqual(E.IO, missed.errno()); - try testing.expectEqualStrings("grep: no match", missed.reply.ename); + try testing.expectEqual(E.NOENT, missed.errno()); + try testing.expectEqualStrings("grep: no match, not found", missed.reply.ename); + const unfound = wr(p, Node.of(serialOf(p), .exec), "Find zzqqnomatchzz\n"); + try testing.expectEqual(E.NOENT, unfound.errno()); + try testing.expectEqualStrings("find: no match, not found", unfound.reply.ename); var after: usize = 0; for (p.panes) |slot| after += @intFromBool(slot != null); try testing.expectEqual(count, after); -- cgit v1.3