Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.1 kB · 263 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264import { LRUCache } from 'lru-cache'import { basename, dirname, join, sep } from 'path'import type { SuggestionItem } from 'src/components/PromptInput/PromptInputFooterSuggestions.js'import { getCwd } from 'src/utils/cwd.js'import { getFsImplementation } from 'src/utils/fsOperations.js'import { logError } from 'src/utils/log.js'import { expandPath } from 'src/utils/path.js'// Typesexport type DirectoryEntry = { name: string path: string type: 'directory'}
export type PathEntry = { name: string path: string type: 'directory' | 'file'}
export type CompletionOptions = { basePath?: string maxResults?: number}
export type PathCompletionOptions = CompletionOptions & { includeFiles?: boolean includeHidden?: boolean}
type ParsedPath = { directory: string prefix: string}
// Cache configurationconst CACHE_SIZE = 500const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
// Initialize LRU cache for directory scansconst directoryCache = new LRUCache<string, DirectoryEntry[]>({ max: CACHE_SIZE, ttl: CACHE_TTL,})
// Initialize LRU cache for path scans (files and directories)const pathCache = new LRUCache<string, PathEntry[]>({ max: CACHE_SIZE, ttl: CACHE_TTL,})
/** * Parses a partial path into directory and prefix components */export function parsePartialPath( partialPath: string, basePath?: string,): ParsedPath { // Handle empty input if (!partialPath) { const directory = basePath || getCwd() return { directory, prefix: '' } }
const resolved = expandPath(partialPath, basePath)
// If path ends with separator, treat as directory with no prefix // Handle both forward slash and platform-specific separator if (partialPath.endsWith('/') || partialPath.endsWith(sep)) { return { directory: resolved, prefix: '' } }
// Split into directory and prefix const directory = dirname(resolved) const prefix = basename(partialPath)
return { directory, prefix }}
/** * Scans a directory and returns subdirectories * Uses LRU cache to avoid repeated filesystem calls */export async function scanDirectory( dirPath: string,): Promise<DirectoryEntry[]> { // Check cache first const cached = directoryCache.get(dirPath) if (cached) { return cached }
try { // Read directory contents const fs = getFsImplementation() const entries = await fs.readdir(dirPath)
// Filter for directories only, exclude hidden directories const directories = entries .filter(entry => entry.isDirectory() && !entry.name.startsWith('.')) .map(entry => ({ name: entry.name, path: join(dirPath, entry.name), type: 'directory' as const, })) .slice(0, 100) // Limit results for MVP
// Cache the results directoryCache.set(dirPath, directories)
return directories } catch (error) { logError(error) return [] }}
/** * Main function to get directory completion suggestions */export async function getDirectoryCompletions( partialPath: string, options: CompletionOptions = {},): Promise<SuggestionItem[]> { const { basePath = getCwd(), maxResults = 10 } = options
const { directory, prefix } = parsePartialPath(partialPath, basePath) const entries = await scanDirectory(directory) const prefixLower = prefix.toLowerCase() const matches = entries .filter(entry => entry.name.toLowerCase().startsWith(prefixLower)) .slice(0, maxResults)
return matches.map(entry => ({ id: entry.path, displayText: entry.name + '/', description: 'directory', metadata: { type: 'directory' as const }, }))}
/** * Clears the directory cache */export function clearDirectoryCache(): void { directoryCache.clear()}
/** * Checks if a string looks like a path (starts with path-like prefixes) */export function isPathLikeToken(token: string): boolean { return ( token.startsWith('~/') || token.startsWith('/') || token.startsWith('./') || token.startsWith('../') || token === '~' || token === '.' || token === '..' )}
/** * Scans a directory and returns both files and subdirectories * Uses LRU cache to avoid repeated filesystem calls */export async function scanDirectoryForPaths( dirPath: string, includeHidden = false,): Promise<PathEntry[]> { const cacheKey = `${dirPath}:${includeHidden}` const cached = pathCache.get(cacheKey) if (cached) { return cached }
try { const fs = getFsImplementation() const entries = await fs.readdir(dirPath)
const paths = entries .filter(entry => includeHidden || !entry.name.startsWith('.')) .map(entry => ({ name: entry.name, path: join(dirPath, entry.name), type: entry.isDirectory() ? ('directory' as const) : ('file' as const), })) .sort((a, b) => { // Sort directories first, then alphabetically if (a.type === 'directory' && b.type !== 'directory') return -1 if (a.type !== 'directory' && b.type === 'directory') return 1 return a.name.localeCompare(b.name) }) .slice(0, 100)
pathCache.set(cacheKey, paths) return paths } catch (error) { logError(error) return [] }}
/** * Get path completion suggestions for files and directories */export async function getPathCompletions( partialPath: string, options: PathCompletionOptions = {},): Promise<SuggestionItem[]> { const { basePath = getCwd(), maxResults = 10, includeFiles = true, includeHidden = false, } = options
const { directory, prefix } = parsePartialPath(partialPath, basePath) const entries = await scanDirectoryForPaths(directory, includeHidden) const prefixLower = prefix.toLowerCase()
const matches = entries .filter(entry => { if (!includeFiles && entry.type === 'file') return false return entry.name.toLowerCase().startsWith(prefixLower) }) .slice(0, maxResults)
// Construct relative path based on original partialPath // e.g., if partialPath is "src/c", directory portion is "src/" // Strip leading "./" since it's just used for cwd search // Handle both forward slash and platform separator for Windows compatibility const hasSeparator = partialPath.includes('/') || partialPath.includes(sep) let dirPortion = '' if (hasSeparator) { // Find the last separator (either / or platform-specific) const lastSlash = partialPath.lastIndexOf('/') const lastSep = partialPath.lastIndexOf(sep) const lastSeparatorPos = Math.max(lastSlash, lastSep) dirPortion = partialPath.substring(0, lastSeparatorPos + 1) } if (dirPortion.startsWith('./') || dirPortion.startsWith('.' + sep)) { dirPortion = dirPortion.slice(2) }
return matches.map(entry => { const fullPath = dirPortion + entry.name return { id: fullPath, displayText: entry.type === 'directory' ? fullPath + '/' : fullPath, metadata: { type: entry.type }, } })}
/** * Clears both directory and path caches */export function clearPathCache(): void { directoryCache.clear() pathCache.clear()}