mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-23 11:34:44 -07:00
feat: detect new files in @ recommendations with watcher based updates (#25256)
This commit is contained in:
@@ -138,6 +138,10 @@ describe('SettingsSchema', () => {
|
||||
getSettingsSchema().context.properties.fileFiltering.properties
|
||||
?.enableRecursiveFileSearch,
|
||||
).toBeDefined();
|
||||
expect(
|
||||
getSettingsSchema().context.properties.fileFiltering.properties
|
||||
?.enableFileWatcher,
|
||||
).toBeDefined();
|
||||
expect(
|
||||
getSettingsSchema().context.properties.fileFiltering.properties
|
||||
?.customIgnoreFilePaths,
|
||||
|
||||
@@ -1471,6 +1471,17 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Respect .geminiignore files when searching.',
|
||||
showInDialog: true,
|
||||
},
|
||||
enableFileWatcher: {
|
||||
type: 'boolean',
|
||||
label: 'Enable File Watcher',
|
||||
category: 'Context',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: oneLine`
|
||||
Enable file watcher updates for @ file suggestions (experimental).
|
||||
`,
|
||||
showInDialog: false,
|
||||
},
|
||||
enableRecursiveFileSearch: {
|
||||
type: 'boolean',
|
||||
label: 'Enable Recursive File Search',
|
||||
|
||||
@@ -553,6 +553,38 @@ describe('useAtCompletion', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should pass enableFileWatcher flag into FileSearchFactory options', async () => {
|
||||
const structure: FileSystemStructure = {
|
||||
src: {
|
||||
'index.ts': '',
|
||||
},
|
||||
};
|
||||
testRootDir = await createTmpDir(structure);
|
||||
|
||||
const createSpy = vi.spyOn(FileSearchFactory, 'create');
|
||||
const configWithWatcher = {
|
||||
getFileFilteringOptions: vi.fn(() => ({
|
||||
respectGitIgnore: true,
|
||||
respectGeminiIgnore: true,
|
||||
enableFileWatcher: true,
|
||||
})),
|
||||
getEnableRecursiveFileSearch: () => true,
|
||||
getFileFilteringEnableFuzzySearch: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
const { result } = await renderHook(() =>
|
||||
useTestHarnessForAtCompletion(true, '', configWithWatcher, testRootDir),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
expect(createSpy).toHaveBeenCalled();
|
||||
const firstCallArg = createSpy.mock.calls[0]?.[0];
|
||||
expect(firstCallArg?.enableFileWatcher).toBe(true);
|
||||
});
|
||||
|
||||
it('should reset and re-initialize when the cwd changes', async () => {
|
||||
const structure1: FileSystemStructure = { 'file1.txt': '' };
|
||||
const rootDir1 = await createTmpDir(structure1);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useEffect, useReducer, useRef } from 'react';
|
||||
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
||||
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
||||
import * as path from 'node:path';
|
||||
import {
|
||||
@@ -224,15 +224,28 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
setIsLoadingSuggestions(state.isLoading);
|
||||
}, [state.isLoading, setIsLoadingSuggestions]);
|
||||
|
||||
const resetFileSearchState = () => {
|
||||
const disposeFileSearchers = useCallback(async () => {
|
||||
const searchers = [...fileSearchMap.current.values()];
|
||||
fileSearchMap.current.clear();
|
||||
initEpoch.current += 1;
|
||||
|
||||
const closePromises: Array<Promise<void>> = [];
|
||||
for (const searcher of searchers) {
|
||||
if (searcher.close) {
|
||||
closePromises.push(searcher.close());
|
||||
}
|
||||
}
|
||||
await Promise.all(closePromises);
|
||||
}, []);
|
||||
|
||||
const resetFileSearchState = useCallback(() => {
|
||||
void disposeFileSearchers();
|
||||
dispatch({ type: 'RESET' });
|
||||
};
|
||||
}, [disposeFileSearchers]);
|
||||
|
||||
useEffect(() => {
|
||||
resetFileSearchState();
|
||||
}, [cwd, config]);
|
||||
}, [cwd, config, resetFileSearchState]);
|
||||
|
||||
useEffect(() => {
|
||||
const workspaceContext = config?.getWorkspaceContext?.();
|
||||
@@ -242,7 +255,18 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
workspaceContext.onDirectoriesChanged(resetFileSearchState);
|
||||
|
||||
return unsubscribe;
|
||||
}, [config]);
|
||||
}, [config, resetFileSearchState]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
void disposeFileSearchers();
|
||||
searchAbortController.current?.abort();
|
||||
if (slowSearchTimer.current) {
|
||||
clearTimeout(slowSearchTimer.current);
|
||||
}
|
||||
},
|
||||
[disposeFileSearchers],
|
||||
);
|
||||
|
||||
// Reacts to user input (`pattern`) ONLY.
|
||||
useEffect(() => {
|
||||
@@ -295,6 +319,8 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
),
|
||||
cache: true,
|
||||
cacheTtl: 30,
|
||||
enableFileWatcher:
|
||||
config?.getFileFilteringOptions()?.enableFileWatcher ?? false,
|
||||
enableRecursiveFileSearch:
|
||||
config?.getEnableRecursiveFileSearch() ?? true,
|
||||
enableFuzzySearch:
|
||||
|
||||
Reference in New Issue
Block a user