summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/useCompletion.ts
diff options
context:
space:
mode:
authorYuki Okita <[email protected]>2025-07-31 05:38:20 +0900
committerGitHub <[email protected]>2025-07-30 20:38:20 +0000
commitc1fe6889569610878c45216556fb99424b5bcba4 (patch)
treeb96f5f66bc00426fcd3e4b87402067342abbce12 /packages/cli/src/ui/hooks/useCompletion.ts
parent21965f986c8aa99da5a0f8e52ae823bb2f040d7a (diff)
feat: Multi-Directory Workspace Support (part1: add `--include-directories` option) (#4605)
Co-authored-by: Allen Hutchison <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/hooks/useCompletion.ts')
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.ts145
1 files changed, 81 insertions, 64 deletions
diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts
index 10724c21..4b106c1b 100644
--- a/packages/cli/src/ui/hooks/useCompletion.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.ts
@@ -43,6 +43,7 @@ export interface UseCompletionReturn {
export function useCompletion(
buffer: TextBuffer,
+ dirs: readonly string[],
cwd: string,
slashCommands: readonly SlashCommand[],
commandContext: CommandContext,
@@ -328,8 +329,6 @@ export function useCompletion(
: partialPath.substring(lastSlashIndex + 1),
);
- const baseDirAbsolute = path.resolve(cwd, baseDirRelative);
-
let isMounted = true;
const findFilesRecursively = async (
@@ -358,7 +357,7 @@ export function useCompletion(
const entryPathRelative = path.join(currentRelativePath, entry.name);
const entryPathFromRoot = path.relative(
- cwd,
+ startDir,
path.join(startDir, entry.name),
);
@@ -417,29 +416,31 @@ export function useCompletion(
respectGitIgnore?: boolean;
respectGeminiIgnore?: boolean;
},
+ searchDir: string,
maxResults = 50,
): Promise<Suggestion[]> => {
const globPattern = `**/${searchPrefix}*`;
const files = await glob(globPattern, {
- cwd,
+ cwd: searchDir,
dot: searchPrefix.startsWith('.'),
nocase: true,
});
const suggestions: Suggestion[] = files
- .map((file: string) => ({
- label: file,
- value: escapePath(file),
- }))
- .filter((s) => {
+ .filter((file) => {
if (fileDiscoveryService) {
- return !fileDiscoveryService.shouldIgnoreFile(
- s.label,
- filterOptions,
- ); // relative path
+ return !fileDiscoveryService.shouldIgnoreFile(file, filterOptions);
}
return true;
})
+ .map((file: string) => {
+ const absolutePath = path.resolve(searchDir, file);
+ const label = path.relative(cwd, absolutePath);
+ return {
+ label,
+ value: escapePath(label),
+ };
+ })
.slice(0, maxResults);
return suggestions;
@@ -456,63 +457,78 @@ export function useCompletion(
config?.getFileFilteringOptions() ?? DEFAULT_FILE_FILTERING_OPTIONS;
try {
- // If there's no slash, or it's the root, do a recursive search from cwd
- if (
- partialPath.indexOf('/') === -1 &&
- prefix &&
- enableRecursiveSearch
- ) {
- if (fileDiscoveryService) {
- fetchedSuggestions = await findFilesWithGlob(
- prefix,
- fileDiscoveryService,
- filterOptions,
- );
+ // If there's no slash, or it's the root, do a recursive search from workspace directories
+ for (const dir of dirs) {
+ let fetchedSuggestionsPerDir: Suggestion[] = [];
+ if (
+ partialPath.indexOf('/') === -1 &&
+ prefix &&
+ enableRecursiveSearch
+ ) {
+ if (fileDiscoveryService) {
+ fetchedSuggestionsPerDir = await findFilesWithGlob(
+ prefix,
+ fileDiscoveryService,
+ filterOptions,
+ dir,
+ );
+ } else {
+ fetchedSuggestionsPerDir = await findFilesRecursively(
+ dir,
+ prefix,
+ null,
+ filterOptions,
+ );
+ }
} else {
- fetchedSuggestions = await findFilesRecursively(
- cwd,
- prefix,
- null,
- filterOptions,
- );
- }
- } else {
- // Original behavior: list files in the specific directory
- const lowerPrefix = prefix.toLowerCase();
- const entries = await fs.readdir(baseDirAbsolute, {
- withFileTypes: true,
- });
+ // Original behavior: list files in the specific directory
+ const lowerPrefix = prefix.toLowerCase();
+ const baseDirAbsolute = path.resolve(dir, baseDirRelative);
+ const entries = await fs.readdir(baseDirAbsolute, {
+ withFileTypes: true,
+ });
- // Filter entries using git-aware filtering
- const filteredEntries = [];
- for (const entry of entries) {
- // Conditionally ignore dotfiles
- if (!prefix.startsWith('.') && entry.name.startsWith('.')) {
- continue;
- }
- if (!entry.name.toLowerCase().startsWith(lowerPrefix)) continue;
+ // Filter entries using git-aware filtering
+ const filteredEntries = [];
+ for (const entry of entries) {
+ // Conditionally ignore dotfiles
+ if (!prefix.startsWith('.') && entry.name.startsWith('.')) {
+ continue;
+ }
+ if (!entry.name.toLowerCase().startsWith(lowerPrefix)) continue;
- const relativePath = path.relative(
- cwd,
- path.join(baseDirAbsolute, entry.name),
- );
- if (
- fileDiscoveryService &&
- fileDiscoveryService.shouldIgnoreFile(relativePath, filterOptions)
- ) {
- continue;
+ const relativePath = path.relative(
+ dir,
+ path.join(baseDirAbsolute, entry.name),
+ );
+ if (
+ fileDiscoveryService &&
+ fileDiscoveryService.shouldIgnoreFile(
+ relativePath,
+ filterOptions,
+ )
+ ) {
+ continue;
+ }
+
+ filteredEntries.push(entry);
}
- filteredEntries.push(entry);
+ fetchedSuggestionsPerDir = filteredEntries.map((entry) => {
+ const absolutePath = path.resolve(baseDirAbsolute, entry.name);
+ const label =
+ cwd === dir ? entry.name : path.relative(cwd, absolutePath);
+ const suggestionLabel = entry.isDirectory() ? label + '/' : label;
+ return {
+ label: suggestionLabel,
+ value: escapePath(suggestionLabel),
+ };
+ });
}
-
- fetchedSuggestions = filteredEntries.map((entry) => {
- const label = entry.isDirectory() ? entry.name + '/' : entry.name;
- return {
- label,
- value: escapePath(label), // Value for completion should be just the name part
- };
- });
+ fetchedSuggestions = [
+ ...fetchedSuggestions,
+ ...fetchedSuggestionsPerDir,
+ ];
}
// Like glob, we always return forwardslashes, even in windows.
@@ -585,6 +601,7 @@ export function useCompletion(
};
}, [
buffer.text,
+ dirs,
cwd,
isActive,
resetCompletionState,