diff --git a/packages/pi-coding-agent/src/core/settings-manager.ts b/packages/pi-coding-agent/src/core/settings-manager.ts index d9ae1c2ca..603cd2e93 100644 --- a/packages/pi-coding-agent/src/core/settings-manager.ts +++ b/packages/pi-coding-agent/src/core/settings-manager.ts @@ -141,6 +141,7 @@ export interface Settings { editorPaddingX?: number; // Horizontal padding for input editor (default: 0) autocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5) respectGitignoreInPicker?: boolean; // When false, @ file picker shows gitignored files (default: true) + searchExcludeDirs?: string[]; // Directories to exclude from @ file search (e.g., ["node_modules", ".git", "dist"]) showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME markdown?: MarkdownSettings; memory?: MemorySettings; @@ -1041,6 +1042,16 @@ export class SettingsManager { this.save(); } + getSearchExcludeDirs(): string[] { + return this.settings.searchExcludeDirs ?? []; + } + + setSearchExcludeDirs(dirs: string[]): void { + this.globalSettings.searchExcludeDirs = dirs.filter(Boolean); + this.markModified("searchExcludeDirs"); + this.save(); + } + getCodeBlockIndent(): string { return this.settings.markdown?.codeBlockIndent ?? " "; } diff --git a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts index c3db38b86..0247cfd38 100644 --- a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts @@ -367,7 +367,10 @@ export class InteractiveMode { this.autocompleteProvider = new CombinedAutocompleteProvider( [...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], process.cwd(), - { respectGitignore: this.settingsManager.getRespectGitignoreInPicker() }, + { + respectGitignore: this.settingsManager.getRespectGitignoreInPicker(), + excludeDirs: this.settingsManager.getSearchExcludeDirs(), + }, ); this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider); if (this.editor !== this.defaultEditor) { diff --git a/packages/pi-tui/src/autocomplete.ts b/packages/pi-tui/src/autocomplete.ts index 0fcbdc57e..52ea67c25 100644 --- a/packages/pi-tui/src/autocomplete.ts +++ b/packages/pi-tui/src/autocomplete.ts @@ -131,21 +131,27 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { private commands: (SlashCommand | AutocompleteItem)[]; private basePath: string; private respectGitignore: boolean; + private excludeDirs: Set; constructor( commands: (SlashCommand | AutocompleteItem)[] = [], basePath: string = process.cwd(), - options?: { respectGitignore?: boolean }, + options?: { respectGitignore?: boolean; excludeDirs?: string[] }, ) { this.commands = commands; this.basePath = basePath; this.respectGitignore = options?.respectGitignore ?? true; + this.excludeDirs = new Set(options?.excludeDirs ?? []); } setRespectGitignore(value: boolean): void { this.respectGitignore = value; } + setExcludeDirs(dirs: string[]): void { + this.excludeDirs = new Set(dirs.filter(Boolean)); + } + getSuggestions( lines: string[], cursorLine: number, @@ -485,6 +491,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { continue; } + // Skip excluded directories + if (this.excludeDirs.has(entry.name)) { + continue; + } + // Check if entry is a directory (or a symlink pointing to a directory) let isDirectory = entry.isDirectory(); if (!isDirectory && entry.isSymbolicLink()) { @@ -578,6 +589,13 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { for (const { path: entryPath, isDirectory } of result.matches) { // Native module includes trailing / for directories const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath; + + // Skip paths that start with or contain an excluded directory + if (this.excludeDirs.size > 0) { + const segments = pathWithoutSlash.split("/"); + if (segments.some(seg => this.excludeDirs.has(seg))) continue; + } + const displayPath = scopedQuery ? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash) : pathWithoutSlash;