summaryrefslogtreecommitdiff
path: root/packages/cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src')
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.integration.test.ts24
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.ts52
2 files changed, 68 insertions, 8 deletions
diff --git a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
index 683d3cb1..c38006c3 100644
--- a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
@@ -42,6 +42,7 @@ describe('useCompletion git-aware filtering integration', () => {
shouldIgnoreFile: vi.fn(),
filterFiles: vi.fn(),
getIgnoreInfo: vi.fn(() => ({ gitIgnored: [], customIgnored: [] })),
+ glob: vi.fn().mockResolvedValue([]),
};
mockConfig = {
@@ -225,4 +226,27 @@ describe('useCompletion git-aware filtering integration', () => {
{ label: 'component.tsx', value: 'component.tsx' },
]);
});
+
+ it('should use glob for top-level @ completions when available', async () => {
+ const globResults = [`${testCwd}/src/index.ts`, `${testCwd}/README.md`];
+ mockFileDiscoveryService.glob.mockResolvedValue(globResults);
+
+ const { result } = renderHook(() =>
+ useCompletion('@s', testCwd, true, slashCommands, mockConfig),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ expect(mockFileDiscoveryService.glob).toHaveBeenCalledWith('**/s*', {
+ cwd: testCwd,
+ dot: true,
+ });
+ expect(fs.readdir).not.toHaveBeenCalled(); // Ensure glob is used instead of readdir
+ expect(result.current.suggestions).toEqual([
+ { label: 'README.md', value: 'README.md' },
+ { label: 'src/index.ts', value: 'src/index.ts' },
+ ]);
+ });
});
diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts
index 66457aac..810c6de0 100644
--- a/packages/cli/src/ui/hooks/useCompletion.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.ts
@@ -13,6 +13,7 @@ import {
unescapePath,
getErrorMessage,
Config,
+ FileDiscoveryService,
} from '@gemini-cli/core';
import {
MAX_SUGGESTIONS_TO_SHOW,
@@ -251,21 +252,53 @@ export function useCompletion(
return foundSuggestions.slice(0, maxResults);
};
+ const findFilesWithGlob = async (
+ searchPrefix: string,
+ fileDiscoveryService: FileDiscoveryService,
+ maxResults = 50,
+ ): Promise<Suggestion[]> => {
+ const globPattern = `**/${searchPrefix}*`;
+ const files = await fileDiscoveryService.glob(globPattern, {
+ cwd,
+ dot: true,
+ });
+
+ const suggestions: Suggestion[] = files
+ .map((file: string) => {
+ const relativePath = path.relative(cwd, file);
+ return {
+ label: relativePath,
+ value: escapePath(relativePath),
+ };
+ })
+ .slice(0, maxResults);
+
+ return suggestions;
+ };
+
const fetchSuggestions = async () => {
setIsLoadingSuggestions(true);
let fetchedSuggestions: Suggestion[] = [];
- // Get centralized file discovery service if config is available
- const fileDiscovery = config ? await config.getFileService() : null;
+ const fileDiscoveryService = config
+ ? await config.getFileService()
+ : null;
try {
// If there's no slash, or it's the root, do a recursive search from cwd
if (partialPath.indexOf('/') === -1 && prefix) {
- fetchedSuggestions = await findFilesRecursively(
- cwd,
- prefix,
- fileDiscovery,
- );
+ if (fileDiscoveryService) {
+ fetchedSuggestions = await findFilesWithGlob(
+ prefix,
+ fileDiscoveryService,
+ );
+ } else {
+ fetchedSuggestions = await findFilesRecursively(
+ cwd,
+ prefix,
+ fileDiscoveryService,
+ );
+ }
} else {
// Original behavior: list files in the specific directory
const lowerPrefix = prefix.toLowerCase();
@@ -282,7 +315,10 @@ export function useCompletion(
cwd,
path.join(baseDirAbsolute, entry.name),
);
- if (fileDiscovery && fileDiscovery.shouldIgnoreFile(relativePath)) {
+ if (
+ fileDiscoveryService &&
+ fileDiscoveryService.shouldIgnoreFile(relativePath)
+ ) {
continue;
}