Download packages/cli/src/ui/utils/directoryUtils.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 4.3 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/utils/directoryUtils.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/ui/utils/directoryUtils.ts
-
curl -L -o directoryUtils.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/utils/directoryUtils.ts
4.3 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import * as path from 'node:path'; | |
| import * as fs from 'node:fs'; | |
| import { opendir } from 'node:fs/promises'; | |
| import { homedir, type WorkspaceContext } from '@google/gemini-cli-core'; | |
| const MAX_SUGGESTIONS = 50; | |
| const MATCH_BUFFER_MULTIPLIER = 3; | |
| export function expandHomeDir(p: string): string { | |
| if (!p) { | |
| return ''; | |
| } | |
| let expandedPath = p; | |
| if (p.toLowerCase().startsWith('%userprofile%')) { | |
| expandedPath = homedir() + p.substring('%userprofile%'.length); | |
| } else if (p === '~' || p.startsWith('~/')) { | |
| expandedPath = homedir() + p.substring(1); | |
| } | |
| return path.normalize(expandedPath); | |
| } | |
| interface ParsedPath { | |
| searchDir: string; | |
| filter: string; | |
| isHomeExpansion: boolean; | |
| resultPrefix: string; | |
| } | |
| function parsePartialPath(partialPath: string): ParsedPath { | |
| const isHomeExpansion = partialPath.startsWith('~'); | |
| const expandedPath = expandHomeDir(partialPath || '.'); | |
| let searchDir: string; | |
| let filter: string; | |
| if ( | |
| partialPath === '' || | |
| partialPath.endsWith('/') || | |
| partialPath.endsWith(path.sep) | |
| ) { | |
| searchDir = expandedPath; | |
| filter = ''; | |
| } else { | |
| searchDir = path.dirname(expandedPath); | |
| filter = path.basename(expandedPath); | |
| // Special case for ~ because path.dirname('~') can be '.' | |
| if ( | |
| isHomeExpansion && | |
| !partialPath.includes('/') && | |
| !partialPath.includes(path.sep) | |
| ) { | |
| searchDir = homedir(); | |
| filter = partialPath.substring(1); | |
| } | |
| } | |
| // Calculate result prefix | |
| let resultPrefix = ''; | |
| if ( | |
| partialPath === '' || | |
| partialPath.endsWith('/') || | |
| partialPath.endsWith(path.sep) | |
| ) { | |
| resultPrefix = partialPath; | |
| } else { | |
| const lastSlashIndex = Math.max( | |
| partialPath.lastIndexOf('/'), | |
| partialPath.lastIndexOf(path.sep), | |
| ); | |
| if (lastSlashIndex !== -1) { | |
| resultPrefix = partialPath.substring(0, lastSlashIndex + 1); | |
| } else if (isHomeExpansion) { | |
| resultPrefix = `~${path.sep}`; | |
| } | |
| } | |
| return { searchDir, filter, isHomeExpansion, resultPrefix }; | |
| } | |
| /** | |
| * Gets directory suggestions based on a partial path. | |
| * Uses async iteration with fs.opendir for efficient handling of large directories. | |
| * | |
| * @param partialPath The partial path typed by the user. | |
| * @returns A promise resolving to an array of directory path suggestions. | |
| */ | |
| export async function getDirectorySuggestions( | |
| partialPath: string, | |
| ): Promise<string[]> { | |
| try { | |
| const { searchDir, filter, resultPrefix } = parsePartialPath(partialPath); | |
| if (!fs.existsSync(searchDir) || !fs.statSync(searchDir).isDirectory()) { | |
| return []; | |
| } | |
| const matches: string[] = []; | |
| const filterLower = filter.toLowerCase(); | |
| const showHidden = filter.startsWith('.'); | |
| const dir = await opendir(searchDir); | |
| try { | |
| for await (const entry of dir) { | |
| if (!entry.isDirectory()) { | |
| continue; | |
| } | |
| if (entry.name.startsWith('.') && !showHidden) { | |
| continue; | |
| } | |
| if (entry.name.toLowerCase().startsWith(filterLower)) { | |
| matches.push(entry.name); | |
| // Early termination with buffer for sorting | |
| if (matches.length >= MAX_SUGGESTIONS * MATCH_BUFFER_MULTIPLIER) { | |
| break; | |
| } | |
| } | |
| } | |
| } finally { | |
| await dir.close().catch(() => {}); | |
| } | |
| // Use the separator style from user's input for consistency | |
| const userSep = resultPrefix.includes('/') ? '/' : path.sep; | |
| return matches | |
| .sort() | |
| .slice(0, MAX_SUGGESTIONS) | |
| .map((name) => resultPrefix + name + userSep); | |
| } catch { | |
| return []; | |
| } | |
| } | |
| export interface BatchAddResult { | |
| added: string[]; | |
| errors: string[]; | |
| } | |
| /** | |
| * Helper to batch add directories to the workspace context. | |
| * Handles expansion and error formatting. | |
| */ | |
| export function batchAddDirectories( | |
| workspaceContext: WorkspaceContext, | |
| paths: string[], | |
| ): BatchAddResult { | |
| const result = workspaceContext.addDirectories( | |
| paths.map((p) => expandHomeDir(p.trim())), | |
| ); | |
| const errors: string[] = []; | |
| for (const failure of result.failed) { | |
| errors.push(`Error adding '${failure.path}': ${failure.error.message}`); | |
| } | |
| return { added: result.added, errors }; | |
| } | |