diff options
| author | Bryant Chandler <[email protected]> | 2025-08-07 15:24:55 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-07 22:24:55 +0000 |
| commit | 9fc7115b8654fc193f948570293485f16d89f60a (patch) | |
| tree | 22b9049754e35deabd61ad0923ab3f22a849bff0 /packages/core/src/utils/filesearch | |
| parent | c38147a3a61779ee9b82f656f03b51fc0e68e799 (diff) | |
perf(filesearch): Use async fzf for non-blocking file search (#5771)
Co-authored-by: Jacob Richman <[email protected]>
Diffstat (limited to 'packages/core/src/utils/filesearch')
| -rw-r--r-- | packages/core/src/utils/filesearch/fileSearch.ts | 49 |
1 files changed, 29 insertions, 20 deletions
diff --git a/packages/core/src/utils/filesearch/fileSearch.ts b/packages/core/src/utils/filesearch/fileSearch.ts index 76a099f7..480d5815 100644 --- a/packages/core/src/utils/filesearch/fileSearch.ts +++ b/packages/core/src/utils/filesearch/fileSearch.ts @@ -11,7 +11,7 @@ import picomatch from 'picomatch'; import { Ignore } from './ignore.js'; import { ResultCache } from './result-cache.js'; import * as cache from './crawlCache.js'; -import { Fzf, FzfResultItem } from 'fzf'; +import { AsyncFzf, FzfResultItem } from 'fzf'; export type FileSearchOptions = { projectRoot: string; @@ -78,18 +78,6 @@ export async function filter( return results; } -/** - * Filters a list of paths based on a given pattern using fzf. - * @param allPaths The list of all paths to filter. - * @param pattern The fzf pattern to filter by. - * @returns The filtered and sorted list of paths. - */ -function filterByFzf(allPaths: string[], pattern: string) { - return new Fzf(allPaths) - .find(pattern) - .map((entry: FzfResultItem) => entry.item); -} - export type SearchOptions = { signal?: AbortSignal; maxResults?: number; @@ -105,6 +93,7 @@ export class FileSearch { private readonly ignore: Ignore = new Ignore(); private resultCache: ResultCache | undefined; private allFiles: string[] = []; + private fzf: AsyncFzf<string[]> | undefined; /** * Constructs a new `FileSearch` instance. @@ -136,24 +125,38 @@ export class FileSearch { pattern: string, options: SearchOptions = {}, ): Promise<string[]> { - if (!this.resultCache) { + if (!this.resultCache || !this.fzf) { throw new Error('Engine not initialized. Call initialize() first.'); } pattern = pattern || '*'; + let filteredCandidates; const { files: candidates, isExactMatch } = await this.resultCache!.get(pattern); - let filteredCandidates; if (isExactMatch) { + // Use the cached result. filteredCandidates = candidates; } else { - // Apply the user's picomatch pattern filter - filteredCandidates = pattern.includes('*') - ? await filter(candidates, pattern, options.signal) - : filterByFzf(this.allFiles, pattern); - this.resultCache!.set(pattern, filteredCandidates); + let shouldCache = true; + if (pattern.includes('*')) { + filteredCandidates = await filter(candidates, pattern, options.signal); + } else { + filteredCandidates = await this.fzf + .find(pattern) + .then((results: Array<FzfResultItem<string>>) => + results.map((entry: FzfResultItem<string>) => entry.item), + ) + .catch(() => { + shouldCache = false; + return []; + }); + } + + if (shouldCache) { + this.resultCache!.set(pattern, filteredCandidates); + } } // Trade-off: We apply a two-stage filtering process. @@ -287,5 +290,11 @@ export class FileSearch { */ private buildResultCache(): void { this.resultCache = new ResultCache(this.allFiles, this.absoluteDir); + // The v1 algorithm is much faster since it only looks at the first + // occurence of the pattern. We use it for search spaces that have >20k + // files, because the v2 algorithm is just too slow in those cases. + this.fzf = new AsyncFzf(this.allFiles, { + fuzzy: this.allFiles.length > 20000 ? 'v1' : 'v2', + }); } } |
