From 5e5ba2f2f2696b3c58971b7f3765bf1c189f3ecc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 10 Nov 2025 18:01:59 +0100 Subject: [PATCH] terminal suggest - adopt `vscode.fs` watcher (#276477) * terminal suggest - adopt `vscode.fs` watcher * Update extensions/terminal-suggest/src/env/pathExecutableCache.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * clean it up --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/env/pathExecutableCache.ts | 44 -------------- .../src/terminalSuggestMain.ts | 58 +++++++++++++++++-- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/extensions/terminal-suggest/src/env/pathExecutableCache.ts index 6576bb59894..9ca3d0ea588 100644 --- a/extensions/terminal-suggest/src/env/pathExecutableCache.ts +++ b/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -10,8 +10,6 @@ import { osIsWindows } from '../helpers/os'; import type { ICompletionResource } from '../types'; import { getFriendlyResourcePath } from '../helpers/uri'; import { SettingsIds } from '../constants'; -import * as filesystem from 'fs'; -import * as path from 'path'; import { TerminalShellType } from '../terminalSuggestMain'; const isWindows = osIsWindows(); @@ -220,46 +218,4 @@ export class PathExecutableCache implements vscode.Disposable { } } -export async function watchPathDirectories(context: vscode.ExtensionContext, env: ITerminalEnvironment, pathExecutableCache: PathExecutableCache | undefined): Promise { - const pathDirectories = new Set(); - - const envPath = env.PATH; - if (envPath) { - envPath.split(path.delimiter).forEach(p => pathDirectories.add(p)); - } - - const activeWatchers = new Set(); - - // Watch each directory - for (const dir of pathDirectories) { - try { - if (activeWatchers.has(dir)) { - // Skip if already watching or directory doesn't exist - continue; - } - - const stat = await fs.stat(dir); - if (!stat.isDirectory()) { - continue; - } - - const watcher = filesystem.watch(dir, { persistent: false }, () => { - if (pathExecutableCache) { - // Refresh cache when directory contents change - pathExecutableCache.refresh(dir); - } - }); - - activeWatchers.add(dir); - - context.subscriptions.push(new vscode.Disposable(() => { - try { - watcher.close(); - activeWatchers.delete(dir); - } catch { } { } - })); - } catch { } - } -} - export type ITerminalEnvironment = { [key: string]: string | undefined }; diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 13f2399d908..0b379eb57f8 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ExecOptionsWithStringEncoding } from 'child_process'; +import * as fs from 'fs'; +import { basename, delimiter } from 'path'; import * as vscode from 'vscode'; import azdSpec from './completions/azd'; import cdSpec from './completions/cd'; @@ -17,7 +19,7 @@ import ghCompletionSpec from './completions/gh'; import npxCompletionSpec from './completions/npx'; import setLocationSpec from './completions/set-location'; import { upstreamSpecs } from './constants'; -import { ITerminalEnvironment, PathExecutableCache, watchPathDirectories } from './env/pathExecutableCache'; +import { ITerminalEnvironment, PathExecutableCache } from './env/pathExecutableCache'; import { executeCommand, executeCommandTimeout, IFigExecuteExternals } from './fig/execute'; import { getFigSuggestions } from './fig/figInterface'; import { createCompletionItem } from './helpers/completionItem'; @@ -30,8 +32,6 @@ import { getPwshGlobals } from './shell/pwsh'; import { getZshGlobals } from './shell/zsh'; import { defaultShellTypeResetChars, getTokenType, shellTypeResetChars, TokenType } from './tokens'; import type { ICompletionResource } from './types'; -import { basename } from 'path'; - export const enum TerminalShellType { Bash = 'bash', Fish = 'fish', @@ -321,13 +321,63 @@ export async function activate(context: vscode.ExtensionContext) { return result.items; } }, '/', '\\')); - await watchPathDirectories(context, currentTerminalEnv, pathExecutableCache); + watchPathDirectories(context, currentTerminalEnv, pathExecutableCache); context.subscriptions.push(vscode.commands.registerCommand('terminal.integrated.suggest.clearCachedGlobals', () => { cachedGlobals.clear(); })); } +async function watchPathDirectories(context: vscode.ExtensionContext, env: ITerminalEnvironment, pathExecutableCache: PathExecutableCache | undefined): Promise { + const pathDirectories = new Set(); + + const envPath = env.PATH; + if (envPath) { + envPath.split(delimiter).forEach(p => pathDirectories.add(p)); + } + + const activeWatchers = new Set(); + + let debounceTimer: NodeJS.Timeout | undefined; // debounce in case many file events fire at once + function handleChange() { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + debounceTimer = setTimeout(() => { + pathExecutableCache?.refresh(); + debounceTimer = undefined; + }, 300); + } + + // Watch each directory + for (const dir of pathDirectories) { + if (activeWatchers.has(dir)) { + // Skip if already watching this directory + continue; + } + + try { + const stat = await fs.promises.stat(dir); + if (!stat.isDirectory()) { + continue; + } + } catch { + // File not found + continue; + } + + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.file(dir), '*')); + context.subscriptions.push( + watcher, + watcher.onDidCreate(() => handleChange()), + watcher.onDidChange(() => handleChange()), + watcher.onDidDelete(() => handleChange()) + ); + + activeWatchers.add(dir); + } +} + /** * Adjusts the current working directory based on a given current command string if it is a folder. * @param currentCommandString - The current command string, which might contain a folder path prefix.