Download src/utils/suggestions/shellHistoryCompletion.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 3.46 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/utils/suggestions/shellHistoryCompletion.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/utils/suggestions/shellHistoryCompletion.ts
-
curl -L -o shellHistoryCompletion.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/utils/suggestions/shellHistoryCompletion.ts
3.46 kB
| import { getHistory } from '../../history.js' | |
| import { logForDebugging } from '../debug.js' | |
| /** | |
| * Result of shell history completion lookup | |
| */ | |
| export type ShellHistoryMatch = { | |
| /** The full command from history */ | |
| fullCommand: string | |
| /** The suffix to display as ghost text (the part after user's input) */ | |
| suffix: string | |
| } | |
| // Cache for shell history commands to avoid repeated async reads | |
| // History only changes when user submits a command, so a long TTL is fine | |
| let shellHistoryCache: string[] | null = null | |
| let shellHistoryCacheTimestamp = 0 | |
| const CACHE_TTL_MS = 60000 // 60 seconds - history won't change while typing | |
| /** | |
| * Get shell commands from history, with caching | |
| */ | |
| async function getShellHistoryCommands(): Promise<string[]> { | |
| const now = Date.now() | |
| // Return cached result if still fresh | |
| if (shellHistoryCache && now - shellHistoryCacheTimestamp < CACHE_TTL_MS) { | |
| return shellHistoryCache | |
| } | |
| const commands: string[] = [] | |
| const seen = new Set<string>() | |
| try { | |
| // Read history entries and filter for bash commands | |
| for await (const entry of getHistory()) { | |
| if (entry.display && entry.display.startsWith('!')) { | |
| // Remove the '!' prefix to get the actual command | |
| const command = entry.display.slice(1).trim() | |
| if (command && !seen.has(command)) { | |
| seen.add(command) | |
| commands.push(command) | |
| } | |
| } | |
| // Limit to 50 most recent unique commands | |
| if (commands.length >= 50) { | |
| break | |
| } | |
| } | |
| } catch (error) { | |
| logForDebugging(`Failed to read shell history: ${error}`) | |
| } | |
| shellHistoryCache = commands | |
| shellHistoryCacheTimestamp = now | |
| return commands | |
| } | |
| /** | |
| * Clear the shell history cache (useful when history is updated) | |
| */ | |
| export function clearShellHistoryCache(): void { | |
| shellHistoryCache = null | |
| shellHistoryCacheTimestamp = 0 | |
| } | |
| /** | |
| * Add a command to the front of the shell history cache without | |
| * flushing the entire cache. If the command already exists in the | |
| * cache it is moved to the front (deduped). When the cache hasn't | |
| * been populated yet this is a no-op – the next lookup will read | |
| * the full history which already includes the new command. | |
| */ | |
| export function prependToShellHistoryCache(command: string): void { | |
| if (!shellHistoryCache) { | |
| return | |
| } | |
| const idx = shellHistoryCache.indexOf(command) | |
| if (idx !== -1) { | |
| shellHistoryCache.splice(idx, 1) | |
| } | |
| shellHistoryCache.unshift(command) | |
| } | |
| /** | |
| * Find the best matching shell command from history for the given input | |
| * | |
| * @param input The current user input (without '!' prefix) | |
| * @returns The best match, or null if no match found | |
| */ | |
| export async function getShellHistoryCompletion( | |
| input: string, | |
| ): Promise<ShellHistoryMatch | null> { | |
| // Don't suggest for empty or very short input | |
| if (!input || input.length < 2) { | |
| return null | |
| } | |
| // Check the trimmed input to make sure there's actual content | |
| const trimmedInput = input.trim() | |
| if (!trimmedInput) { | |
| return null | |
| } | |
| const commands = await getShellHistoryCommands() | |
| // Find the first command that starts with the EXACT input (including spaces) | |
| // This ensures "ls " matches "ls -lah" but "ls " (2 spaces) does not | |
| for (const command of commands) { | |
| if (command.startsWith(input) && command !== input) { | |
| return { | |
| fullCommand: command, | |
| suffix: command.slice(input.length), | |
| } | |
| } | |
| } | |
| return null | |
| } | |