summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/cli/configuration.md6
-rw-r--r--packages/cli/src/config/config.integration.test.ts12
-rw-r--r--packages/cli/src/config/config.ts6
-rw-r--r--packages/cli/src/config/settings.ts1
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.integration.test.ts30
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.ts8
-rw-r--r--packages/core/src/config/config.test.ts4
-rw-r--r--packages/core/src/config/config.ts23
8 files changed, 77 insertions, 13 deletions
diff --git a/docs/cli/configuration.md b/docs/cli/configuration.md
index e3c379b0..b4dd2e95 100644
--- a/docs/cli/configuration.md
+++ b/docs/cli/configuration.md
@@ -56,13 +56,15 @@ In addition to a project settings file, a project's `.gemini` directory can cont
- **`fileFiltering`** (object):
- **Description:** Controls git-aware file filtering behavior for @ commands and file discovery tools.
- - **Default:** `"respectGitIgnore": true`
+ - **Default:** `"respectGitIgnore": true, "enableRecursiveFileSearch": true`
- **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.
- **Example:**
```json
"fileFiltering": {
- "respectGitIgnore": true
+ "respectGitIgnore": true,
+ "enableRecursiveFileSearch": false
}
```
diff --git a/packages/cli/src/config/config.integration.test.ts b/packages/cli/src/config/config.integration.test.ts
index de329384..868538ab 100644
--- a/packages/cli/src/config/config.integration.test.ts
+++ b/packages/cli/src/config/config.integration.test.ts
@@ -75,7 +75,9 @@ describe('Configuration Integration Tests', () => {
sandbox: false,
targetDir: tempDir,
debugMode: false,
- fileFilteringRespectGitIgnore: false,
+ fileFiltering: {
+ respectGitIgnore: false,
+ },
};
const config = new Config(configParams);
@@ -109,7 +111,9 @@ describe('Configuration Integration Tests', () => {
sandbox: false,
targetDir: tempDir,
debugMode: false,
- fileFilteringRespectGitIgnore: false,
+ fileFiltering: {
+ respectGitIgnore: false,
+ },
};
const config = new Config(configParams);
@@ -178,7 +182,9 @@ describe('Configuration Integration Tests', () => {
sandbox: false,
targetDir: tempDir,
debugMode: false,
- fileFilteringRespectGitIgnore: false, // CI might need to see all files
+ fileFiltering: {
+ respectGitIgnore: false,
+ }, // CI might need to see all files
};
const config = new Config(configParams);
diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts
index afc63b78..26894bc9 100644
--- a/packages/cli/src/config/config.ts
+++ b/packages/cli/src/config/config.ts
@@ -228,7 +228,11 @@ export async function loadCliConfig(
logPrompts: argv.telemetryLogPrompts ?? settings.telemetry?.logPrompts,
},
// Git-aware file filtering settings
- fileFilteringRespectGitIgnore: settings.fileFiltering?.respectGitIgnore,
+ fileFiltering: {
+ respectGitIgnore: settings.fileFiltering?.respectGitIgnore,
+ enableRecursiveFileSearch:
+ settings.fileFiltering?.enableRecursiveFileSearch,
+ },
checkpointing: argv.checkpointing || settings.checkpointing?.enabled,
proxy:
process.env.HTTPS_PROXY ||
diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts
index b63f5bb6..b149216a 100644
--- a/packages/cli/src/config/settings.ts
+++ b/packages/cli/src/config/settings.ts
@@ -56,6 +56,7 @@ export interface Settings {
// Git-aware file filtering settings
fileFiltering?: {
respectGitIgnore?: boolean;
+ enableRecursiveFileSearch?: boolean;
};
// UI setting. Does not display the ANSI-controlled terminal title.
diff --git a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
index 40af5d4f..237b6aae 100644
--- a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
@@ -47,6 +47,7 @@ describe('useCompletion git-aware filtering integration', () => {
mockConfig = {
getFileFilteringRespectGitIgnore: vi.fn(() => true),
getFileService: vi.fn().mockReturnValue(mockFileDiscoveryService),
+ getEnableRecursiveFileSearch: vi.fn(() => true),
};
vi.mocked(FileDiscoveryService).mockImplementation(
@@ -170,6 +171,35 @@ describe('useCompletion git-aware filtering integration', () => {
);
});
+ it('should not perform recursive search when disabled in config', async () => {
+ const globResults = [`${testCwd}/data`, `${testCwd}/dist`];
+ vi.mocked(glob).mockResolvedValue(globResults);
+
+ // Disable recursive search in the mock config
+ const mockConfigNoRecursive = {
+ ...mockConfig,
+ getEnableRecursiveFileSearch: vi.fn(() => false),
+ };
+
+ vi.mocked(fs.readdir).mockResolvedValue([
+ { name: 'data', isDirectory: () => true },
+ { name: 'dist', isDirectory: () => true },
+ ] as Array<{ name: string; isDirectory: () => boolean }>);
+
+ renderHook(() =>
+ useCompletion('@d', testCwd, true, slashCommands, mockConfigNoRecursive),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ // `glob` should not be called because recursive search is disabled
+ expect(glob).not.toHaveBeenCalled();
+ // `fs.readdir` should be called for the top-level directory instead
+ expect(fs.readdir).toHaveBeenCalledWith(testCwd, { withFileTypes: true });
+ });
+
it('should work without config (fallback behavior)', async () => {
vi.mocked(fs.readdir).mockResolvedValue([
{ name: 'src', isDirectory: () => true },
diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts
index 7cbe8a7e..eef54fc8 100644
--- a/packages/cli/src/ui/hooks/useCompletion.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.ts
@@ -322,10 +322,16 @@ export function useCompletion(
let fetchedSuggestions: Suggestion[] = [];
const fileDiscoveryService = config ? config.getFileService() : null;
+ const enableRecursiveSearch =
+ config?.getEnableRecursiveFileSearch() ?? true;
try {
// If there's no slash, or it's the root, do a recursive search from cwd
- if (partialPath.indexOf('/') === -1 && prefix) {
+ if (
+ partialPath.indexOf('/') === -1 &&
+ prefix &&
+ enableRecursiveSearch
+ ) {
if (fileDiscoveryService) {
fetchedSuggestions = await findFilesWithGlob(
prefix,
diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts
index 8a9f038c..9b576b96 100644
--- a/packages/core/src/config/config.test.ts
+++ b/packages/core/src/config/config.test.ts
@@ -163,7 +163,9 @@ describe('Server Config (config.ts)', () => {
it('should set custom file filtering settings when provided', () => {
const paramsWithFileFiltering: ConfigParameters = {
...baseParams,
- fileFilteringRespectGitIgnore: false,
+ fileFiltering: {
+ respectGitIgnore: false,
+ },
};
const config = new Config(paramsWithFileFiltering);
expect(config.getFileFilteringRespectGitIgnore()).toBe(false);
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts
index 514fc717..be21ac8c 100644
--- a/packages/core/src/config/config.ts
+++ b/packages/core/src/config/config.ts
@@ -104,7 +104,10 @@ export interface ConfigParameters {
contextFileName?: string | string[];
accessibility?: AccessibilitySettings;
telemetry?: TelemetrySettings;
- fileFilteringRespectGitIgnore?: boolean;
+ fileFiltering?: {
+ respectGitIgnore?: boolean;
+ enableRecursiveFileSearch?: boolean;
+ };
checkpointing?: boolean;
proxy?: string;
cwd: string;
@@ -136,7 +139,10 @@ export class Config {
private readonly accessibility: AccessibilitySettings;
private readonly telemetrySettings: TelemetrySettings;
private geminiClient!: GeminiClient;
- private readonly fileFilteringRespectGitIgnore: boolean;
+ private readonly fileFiltering: {
+ respectGitIgnore: boolean;
+ enableRecursiveFileSearch: boolean;
+ };
private fileDiscoveryService: FileDiscoveryService | null = null;
private gitService: GitService | undefined = undefined;
private readonly checkpointing: boolean;
@@ -172,8 +178,11 @@ export class Config {
logPrompts: params.telemetry?.logPrompts ?? true,
};
- this.fileFilteringRespectGitIgnore =
- params.fileFilteringRespectGitIgnore ?? true;
+ this.fileFiltering = {
+ respectGitIgnore: params.fileFiltering?.respectGitIgnore ?? true,
+ enableRecursiveFileSearch:
+ params.fileFiltering?.enableRecursiveFileSearch ?? true,
+ };
this.checkpointing = params.checkpointing ?? false;
this.proxy = params.proxy;
this.cwd = params.cwd ?? process.cwd();
@@ -330,8 +339,12 @@ export class Config {
return getProjectTempDir(this.getProjectRoot());
}
+ getEnableRecursiveFileSearch(): boolean {
+ return this.fileFiltering.enableRecursiveFileSearch;
+ }
+
getFileFilteringRespectGitIgnore(): boolean {
- return this.fileFilteringRespectGitIgnore;
+ return this.fileFiltering.respectGitIgnore;
}
getCheckpointingEnabled(): boolean {