summaryrefslogtreecommitdiff
path: root/packages/cli/src
diff options
context:
space:
mode:
authorBryant Chandler <[email protected]>2025-08-18 13:43:24 -0700
committerGitHub <[email protected]>2025-08-18 20:43:24 +0000
commit465ac9f547d0d684439886d1466c1a1133da611d (patch)
treeb94f00730118784b5b07800db71224816b444bfe /packages/cli/src
parentd66ddcd82e09d7b6fbc0226e31d73d38db5cff2a (diff)
feat(filesearch): Introduce non-recursive file search strategy (#6087)
Co-authored-by: Jacob Richman <[email protected]> Co-authored-by: Bryant Chandler <[email protected]>
Diffstat (limited to 'packages/cli/src')
-rw-r--r--packages/cli/src/ui/hooks/useAtCompletion.test.ts64
-rw-r--r--packages/cli/src/ui/hooks/useAtCompletion.ts14
2 files changed, 51 insertions, 27 deletions
diff --git a/packages/cli/src/ui/hooks/useAtCompletion.test.ts b/packages/cli/src/ui/hooks/useAtCompletion.test.ts
index 599f8fdf..b7ce4470 100644
--- a/packages/cli/src/ui/hooks/useAtCompletion.test.ts
+++ b/packages/cli/src/ui/hooks/useAtCompletion.test.ts
@@ -9,7 +9,7 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { useAtCompletion } from './useAtCompletion.js';
-import { Config, FileSearch } from '@google/gemini-cli-core';
+import { Config, FileSearch, FileSearchFactory } from '@google/gemini-cli-core';
import {
createTmpDir,
cleanupTmpDir,
@@ -190,14 +190,25 @@ describe('useAtCompletion', () => {
const structure: FileSystemStructure = { 'a.txt': '', 'b.txt': '' };
testRootDir = await createTmpDir(structure);
- // Spy on the search method to introduce an artificial delay
- const originalSearch = FileSearch.prototype.search;
- vi.spyOn(FileSearch.prototype, 'search').mockImplementation(
- async function (...args) {
+ const realFileSearch = FileSearchFactory.create({
+ projectRoot: testRootDir,
+ ignoreDirs: [],
+ useGitignore: true,
+ useGeminiignore: true,
+ cache: false,
+ cacheTtl: 0,
+ enableRecursiveFileSearch: true,
+ });
+ await realFileSearch.initialize();
+
+ const mockFileSearch: FileSearch = {
+ initialize: vi.fn().mockResolvedValue(undefined),
+ search: vi.fn().mockImplementation(async (...args) => {
await new Promise((resolve) => setTimeout(resolve, 300));
- return originalSearch.apply(this, args);
- },
- );
+ return realFileSearch.search(...args);
+ }),
+ };
+ vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
const { result, rerender } = renderHook(
({ pattern }) =>
@@ -241,14 +252,15 @@ describe('useAtCompletion', () => {
testRootDir = await createTmpDir(structure);
const abortSpy = vi.spyOn(AbortController.prototype, 'abort');
- const searchSpy = vi
- .spyOn(FileSearch.prototype, 'search')
- .mockImplementation(async (...args) => {
- const delay = args[0] === 'a' ? 500 : 50;
+ const mockFileSearch: FileSearch = {
+ initialize: vi.fn().mockResolvedValue(undefined),
+ search: vi.fn().mockImplementation(async (pattern: string) => {
+ const delay = pattern === 'a' ? 500 : 50;
await new Promise((resolve) => setTimeout(resolve, delay));
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return [args[0] as any];
- });
+ return [pattern];
+ }),
+ };
+ vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
const { result, rerender } = renderHook(
({ pattern }) =>
@@ -258,7 +270,10 @@ describe('useAtCompletion', () => {
// Wait for the hook to be ready (initialization is complete)
await waitFor(() => {
- expect(searchSpy).toHaveBeenCalledWith('a', expect.any(Object));
+ expect(mockFileSearch.search).toHaveBeenCalledWith(
+ 'a',
+ expect.any(Object),
+ );
});
// Now that the first search is in-flight, trigger the second one.
@@ -278,9 +293,10 @@ describe('useAtCompletion', () => {
);
// The search spy should have been called for both patterns.
- expect(searchSpy).toHaveBeenCalledWith('b', expect.any(Object));
-
- vi.restoreAllMocks();
+ expect(mockFileSearch.search).toHaveBeenCalledWith(
+ 'b',
+ expect.any(Object),
+ );
});
});
@@ -313,9 +329,13 @@ describe('useAtCompletion', () => {
testRootDir = await createTmpDir({});
// Force an error during initialization
- vi.spyOn(FileSearch.prototype, 'initialize').mockRejectedValueOnce(
- new Error('Initialization failed'),
- );
+ const mockFileSearch: FileSearch = {
+ initialize: vi
+ .fn()
+ .mockRejectedValue(new Error('Initialization failed')),
+ search: vi.fn(),
+ };
+ vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
const { result, rerender } = renderHook(
({ enabled }) =>
diff --git a/packages/cli/src/ui/hooks/useAtCompletion.ts b/packages/cli/src/ui/hooks/useAtCompletion.ts
index f6835dc8..5a2571a0 100644
--- a/packages/cli/src/ui/hooks/useAtCompletion.ts
+++ b/packages/cli/src/ui/hooks/useAtCompletion.ts
@@ -5,7 +5,12 @@
*/
import { useEffect, useReducer, useRef } from 'react';
-import { Config, FileSearch, escapePath } from '@google/gemini-cli-core';
+import {
+ Config,
+ FileSearch,
+ FileSearchFactory,
+ escapePath,
+} from '@google/gemini-cli-core';
import {
Suggestion,
MAX_SUGGESTIONS_TO_SHOW,
@@ -156,7 +161,7 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
useEffect(() => {
const initialize = async () => {
try {
- const searcher = new FileSearch({
+ const searcher = FileSearchFactory.create({
projectRoot: cwd,
ignoreDirs: [],
useGitignore:
@@ -165,9 +170,8 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
config?.getFileFilteringOptions()?.respectGeminiIgnore ?? true,
cache: true,
cacheTtl: 30, // 30 seconds
- maxDepth: !(config?.getEnableRecursiveFileSearch() ?? true)
- ? 0
- : undefined,
+ enableRecursiveFileSearch:
+ config?.getEnableRecursiveFileSearch() ?? true,
});
await searcher.initialize();
fileSearch.current = searcher;