mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
perf(filesearch): Use async fzf for non-blocking file search (#5771)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user