mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(search): Add option to disable fuzzy search (#6510)
Co-authored-by: Jacob Richman <jacob314@gmail.com> Co-authored-by: Arya Gummadi <aryagummadi@google.com>
This commit is contained in:
@@ -62,14 +62,26 @@ In addition to a project settings file, a project's `.gemini` directory can cont
|
||||
- **Properties:**
|
||||
- **`respectGitIgnore`** (boolean): Whether to respect .gitignore patterns when discovering files. When set to `true`, git-ignored files (like `node_modules/`, `dist/`, `.env`) are automatically excluded from @ commands and file listing operations.
|
||||
- **`enableRecursiveFileSearch`** (boolean): Whether to enable searching recursively for filenames under the current tree when completing @ prefixes in the prompt.
|
||||
- **`disableFuzzySearch`** (boolean): When `true`, disables the fuzzy search capabilities when searching for files, which can improve performance on projects with a large number of files.
|
||||
- **Example:**
|
||||
```json
|
||||
"fileFiltering": {
|
||||
"respectGitIgnore": true,
|
||||
"enableRecursiveFileSearch": false
|
||||
"enableRecursiveFileSearch": false,
|
||||
"disableFuzzySearch": true
|
||||
}
|
||||
```
|
||||
|
||||
### Troubleshooting File Search Performance
|
||||
|
||||
If you are experiencing performance issues with file searching (e.g., with `@` completions), especially in projects with a very large number of files, here are a few things you can try in order of recommendation:
|
||||
|
||||
1. **Use `.geminiignore`:** Create a `.geminiignore` file in your project root to exclude directories that contain a large number of files that you don't need to reference (e.g., build artifacts, logs, `node_modules`). Reducing the total number of files crawled is the most effective way to improve performance.
|
||||
|
||||
2. **Disable Fuzzy Search:** If ignoring files is not enough, you can disable fuzzy search by setting `disableFuzzySearch` to `true` in your `settings.json` file. This will use a simpler, non-fuzzy matching algorithm, which can be faster.
|
||||
|
||||
3. **Disable Recursive File Search:** As a last resort, you can disable recursive file search entirely by setting `enableRecursiveFileSearch` to `false`. This will be the fastest option as it avoids a recursive crawl of your project. However, it means you will need to type the full path to files when using `@` completions.
|
||||
|
||||
- **`coreTools`** (array of strings):
|
||||
- **Description:** Allows you to specify a list of core tool names that should be made available to the model. This can be used to restrict the set of built-in tools. See [Built-in Tools](../core/tools-api.md#built-in-tools) for a list of core tools. You can also specify command-specific restrictions for tools that support it, like the `ShellTool`. For example, `"coreTools": ["ShellTool(ls -l)"]` will only allow the `ls -l` command to be executed.
|
||||
- **Default:** All tools available for use by the Gemini model.
|
||||
|
||||
@@ -274,6 +274,15 @@ export const SETTINGS_SCHEMA = {
|
||||
description: 'Enable recursive file search functionality',
|
||||
showInDialog: true,
|
||||
},
|
||||
disableFuzzySearch: {
|
||||
type: 'boolean',
|
||||
label: 'Disable Fuzzy Search',
|
||||
category: 'File Filtering',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Disable fuzzy search when searching for files.',
|
||||
showInDialog: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ describe('useAtCompletion', () => {
|
||||
respectGeminiIgnore: true,
|
||||
})),
|
||||
getEnableRecursiveFileSearch: () => true,
|
||||
getFileFilteringDisableFuzzySearch: () => false,
|
||||
} as unknown as Config;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
@@ -198,6 +199,7 @@ describe('useAtCompletion', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
await realFileSearch.initialize();
|
||||
|
||||
@@ -468,6 +470,7 @@ describe('useAtCompletion', () => {
|
||||
respectGitIgnore: true,
|
||||
respectGeminiIgnore: true,
|
||||
})),
|
||||
getFileFilteringDisableFuzzySearch: () => false,
|
||||
} as unknown as Config;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
|
||||
@@ -172,6 +172,8 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
cacheTtl: 30, // 30 seconds
|
||||
enableRecursiveFileSearch:
|
||||
config?.getEnableRecursiveFileSearch() ?? true,
|
||||
disableFuzzySearch:
|
||||
config?.getFileFilteringDisableFuzzySearch() ?? false,
|
||||
});
|
||||
await searcher.initialize();
|
||||
fileSearch.current = searcher;
|
||||
|
||||
@@ -177,6 +177,7 @@ export interface ConfigParameters {
|
||||
respectGitIgnore?: boolean;
|
||||
respectGeminiIgnore?: boolean;
|
||||
enableRecursiveFileSearch?: boolean;
|
||||
disableFuzzySearch?: boolean;
|
||||
};
|
||||
checkpointing?: boolean;
|
||||
proxy?: string;
|
||||
@@ -237,6 +238,7 @@ export class Config {
|
||||
respectGitIgnore: boolean;
|
||||
respectGeminiIgnore: boolean;
|
||||
enableRecursiveFileSearch: boolean;
|
||||
disableFuzzySearch: boolean;
|
||||
};
|
||||
private fileDiscoveryService: FileDiscoveryService | null = null;
|
||||
private gitService: GitService | undefined = undefined;
|
||||
@@ -316,6 +318,7 @@ export class Config {
|
||||
respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ?? true,
|
||||
enableRecursiveFileSearch:
|
||||
params.fileFiltering?.enableRecursiveFileSearch ?? true,
|
||||
disableFuzzySearch: params.fileFiltering?.disableFuzzySearch ?? false,
|
||||
};
|
||||
this.checkpointing = params.checkpointing ?? false;
|
||||
this.proxy = params.proxy;
|
||||
@@ -600,6 +603,10 @@ export class Config {
|
||||
return this.fileFiltering.enableRecursiveFileSearch;
|
||||
}
|
||||
|
||||
getFileFilteringDisableFuzzySearch(): boolean {
|
||||
return this.fileFiltering.disableFuzzySearch;
|
||||
}
|
||||
|
||||
getFileFilteringRespectGitIgnore(): boolean {
|
||||
return this.fileFiltering.respectGitIgnore;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -57,6 +58,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -84,6 +86,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -112,6 +115,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -144,6 +148,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -167,6 +172,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -201,6 +207,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -230,6 +237,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -259,6 +267,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
// Expect no errors to be thrown during initialization
|
||||
@@ -285,6 +294,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -310,6 +320,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -318,6 +329,60 @@ describe('FileSearch', () => {
|
||||
expect(results).toEqual(['src/style.css']);
|
||||
});
|
||||
|
||||
it('should not use fzf for fuzzy matching when disableFuzzySearch is true', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: {
|
||||
'file1.js': '',
|
||||
'flexible.js': '',
|
||||
'other.ts': '',
|
||||
},
|
||||
});
|
||||
|
||||
const fileSearch = FileSearchFactory.create({
|
||||
projectRoot: tmpDir,
|
||||
useGitignore: false,
|
||||
useGeminiignore: false,
|
||||
ignoreDirs: [],
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: true,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
const results = await fileSearch.search('fle');
|
||||
|
||||
expect(results).toEqual(['src/flexible.js']);
|
||||
});
|
||||
|
||||
it('should use fzf for fuzzy matching when disableFuzzySearch is false', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: {
|
||||
'file1.js': '',
|
||||
'flexible.js': '',
|
||||
'other.ts': '',
|
||||
},
|
||||
});
|
||||
|
||||
const fileSearch = FileSearchFactory.create({
|
||||
projectRoot: tmpDir,
|
||||
useGitignore: false,
|
||||
useGeminiignore: false,
|
||||
ignoreDirs: [],
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
const results = await fileSearch.search('fle');
|
||||
|
||||
expect(results).toEqual(
|
||||
expect.arrayContaining(['src/file1.js', 'src/flexible.js']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty array when no matches are found', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: ['file1.js'],
|
||||
@@ -331,6 +396,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -361,6 +427,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await expect(fileSearch.search('')).rejects.toThrow(
|
||||
@@ -382,6 +449,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -404,6 +472,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -427,6 +496,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -463,6 +533,7 @@ describe('FileSearch', () => {
|
||||
cache: true, // Enable caching for this test
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -502,6 +573,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -545,6 +617,7 @@ describe('FileSearch', () => {
|
||||
cache: true, // Ensure caching is enabled
|
||||
cacheTtl: 10000,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -582,6 +655,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -611,6 +685,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -635,6 +710,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -659,6 +735,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -681,6 +758,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface FileSearchOptions {
|
||||
cache: boolean;
|
||||
cacheTtl: number;
|
||||
enableRecursiveFileSearch: boolean;
|
||||
disableFuzzySearch: boolean;
|
||||
maxDepth?: number;
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ class RecursiveFileSearch implements FileSearch {
|
||||
filteredCandidates = candidates;
|
||||
} else {
|
||||
let shouldCache = true;
|
||||
if (pattern.includes('*')) {
|
||||
if (pattern.includes('*') || this.options.disableFuzzySearch) {
|
||||
filteredCandidates = await filter(candidates, pattern, options.signal);
|
||||
} else {
|
||||
filteredCandidates = await this.fzf
|
||||
|
||||
Reference in New Issue
Block a user