diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/look.zig | 35 | ||||
| -rw-r--r-- | src/pardes.zig | 29 |
2 files changed, 51 insertions, 13 deletions
diff --git a/src/look.zig b/src/look.zig index 61970de1..2b788bde 100644 --- a/src/look.zig +++ b/src/look.zig @@ -179,13 +179,24 @@ const find_max_hits = 512; const find_max_depth = 16; const find_max_steps = 100_000; +/// Directories a source tree has no answers in, skipped whole. fd reads +/// .gitignore for this; pardes has no ignore parser, and every one of these +/// costs a real search: agave's `target/` alone is 456_000 of its 460_000 +/// entries and holds 1_200 of the 1_242 paths matching "bank", so a Find for +/// `bank` burned the whole 512-hit budget on build artifacts and never +/// reached `runtime/src/bank.rs`. That looked like a broken matcher. +const find_skip = [_][]const u8{ + ".git", ".jj", "target", "node_modules", + ".venv", "__pycache__", ".zig-cache", "zig-out", +}; + /// `fd`, in-core: every path under `dir` whose NAME contains `pat` (plain /// case-insensitive substring — fd's default is a regex and pardes has no -/// regex engine to spend on one), one ABSOLUTE path per line into `out`. -/// Absolute because each line is an ordinary look target that outlives the -/// walk: a terminal's cwd can move out from under its own results buffer. -/// Only real directories are entered, so a symlink can never close a cycle, -/// and `.git` is skipped whole (fd's default too). +/// regex engine to spend on one), one path per line into `out`, RELATIVE to +/// `dir` — the results buffer is itself named `dir/+Search`, so every row +/// resolves against the same directory the walk started in and reads as the +/// short name the searcher was looking for. Only real directories are +/// entered, so a symlink can never close a cycle. pub fn find(arena: std.mem.Allocator, dir: []const u8, pat: []const u8, out: *std.ArrayList(u8)) void { var hits: std.ArrayList([]const u8) = .empty; if (platform_has_fs) { @@ -196,19 +207,19 @@ pub fn find(arena: std.mem.Allocator, dir: []const u8, pat: []const u8, out: *st var root = std.Io.Dir.cwd().openDir(io, dir, .{ .iterate = true }) catch return; defer root.close(io); // walkSelectively, not walk: descending is opt-in, which is the only - // way to express the depth cap and the .git skip at all. + // way to express the depth cap and find_skip at all. var w = root.walkSelectively(arena) catch return; defer w.deinit(); var steps: usize = 0; - while (steps < find_max_steps and hits.items.len < find_max_hits) { + walk: while (steps < find_max_steps and hits.items.len < find_max_hits) { steps += 1; // an unreadable dir burns a step too, so it cannot spin const e = (w.next(io) catch continue) orelse break; if (std.ascii.indexOfIgnoreCase(e.basename, pat) != null) - hits.append(arena, std.fmt.allocPrint(arena, "{s}/{s}", .{ - std.mem.trimEnd(u8, dir, "/"), e.path, - }) catch break) catch break; - if (e.kind == .directory and e.depth() < find_max_depth and !std.mem.eql(u8, e.basename, ".git")) - w.enter(io, e) catch {}; + // e.path points into the walker's own buffer, dead at next() + hits.append(arena, arena.dupe(u8, e.path) catch break) catch break; + if (e.kind != .directory or e.depth() >= find_max_depth) continue; + for (find_skip) |s| if (std.mem.eql(u8, e.basename, s)) continue :walk; + w.enter(io, e) catch {}; } } else { // web: the build-generated source archive IS the filesystem, and it is diff --git a/src/pardes.zig b/src/pardes.zig index 4d821e60..8e644470 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -2640,8 +2640,10 @@ pub const Pardes = struct { // a real file names itself; a terminal or an output buffer has no path const has_path = if (pane.file) |f| !f.output else false; var idbuf: [16]u8 = undefined; + // relative to `dir`, like Find's rows — and `dir` IS this file's + // own directory, so that is exactly its basename const loc: []const u8 = if (has_path) - pane.file.?.path + std.fs.path.basename(pane.file.?.path) else std.fmt.bufPrint(&idbuf, "@p{d}", .{id}) catch return; for (pl.lines, 0..) |ln, i| { @@ -2663,6 +2665,19 @@ pub const Pardes = struct { sp.cur_row = 0; sp.msel.active = false; pane.search_row = null; + // a new result count is a new height (splitBelow does this for a + // fresh one): trade rows with the neighbour it was split off, in + // BOTH directions so the pane grows back for a wider search. The + // neighbour keeps a tag + 2 rows whatever the hit count. + if (p.layoutFindTerm(sid)) |loc| if (p.col_n[loc.col] > 1) { + p.snapColWeights(loc.col); + if (p.panes[p.col_terms[loc.col][if (loc.idx > 0) loc.idx - 1 else 1]]) |sib| { + const pair = sp.vweight + sib.vweight; + const hi = @max(@as(f32, BOX_H + 1), pair - @as(f32, BOX_H + 2)); + sp.vweight = @min(hi, @as(f32, @floatFromInt(BOX_H + fileLineCount(std.mem.trimEnd(u8, content, "\n"))))); + sib.vweight = pair - sp.vweight; + } + }; return; }; const free = p.freeSlot() orelse { @@ -4616,6 +4631,18 @@ pub const Pardes = struct { }; src.vweight = @floatFromInt(BOX_H + keep); nw.vweight = @floatFromInt(@max(1, src_h -| (BOX_H + keep))); + // an output buffer is worth exactly its own text: a three-hit +Search + // is four rows, not half the source. Nothing else can want the rows, + // so they go straight back to the pane they were taken from. + if (nw.file) |f| if (f.output) { + // trimmed: every row ends in a newline, and the empty line after + // the last one is not a result + const want: f32 = @floatFromInt(BOX_H + fileLineCount(std.mem.trimEnd(u8, f.content, "\n"))); + if (want < nw.vweight) { + src.vweight += nw.vweight - want; + nw.vweight = want; + } + }; } /// a shell nobody has touched: no typing, cursor on the first prompt line, |
