@@ -16,20 +16,30 @@ import { TerminalShellType } from '../terminalSuggestMain';
1616
1717const isWindows = osIsWindows ( ) ;
1818
19+ export interface IExecutablesInPath {
20+ completionResources : Set < ICompletionResource > | undefined ;
21+ labels : Set < string > | undefined ;
22+ }
23+
1924export class PathExecutableCache implements vscode . Disposable {
2025 private _disposables : vscode . Disposable [ ] = [ ] ;
2126
22- private _cachedPathValue : string | undefined ;
2327 private _cachedWindowsExeExtensions : { [ key : string ] : boolean | undefined } | undefined ;
24- private _cachedExes : { completionResources : Set < ICompletionResource > | undefined ; labels : Set < string > | undefined } | undefined ;
28+ private _cachedExes : Map < string , Set < ICompletionResource > | undefined > = new Map ( ) ;
29+
30+ private _inProgressRequest : {
31+ env : ITerminalEnvironment ;
32+ shellType : TerminalShellType | undefined ;
33+ promise : Promise < IExecutablesInPath | undefined > ;
34+ } | undefined ;
2535
2636 constructor ( ) {
2737 if ( isWindows ) {
2838 this . _cachedWindowsExeExtensions = vscode . workspace . getConfiguration ( SettingsIds . SuggestPrefix ) . get ( SettingsIds . CachedWindowsExecutableExtensionsSuffixOnly ) ;
2939 this . _disposables . push ( vscode . workspace . onDidChangeConfiguration ( e => {
3040 if ( e . affectsConfiguration ( SettingsIds . CachedWindowsExecutableExtensions ) ) {
3141 this . _cachedWindowsExeExtensions = vscode . workspace . getConfiguration ( SettingsIds . SuggestPrefix ) . get ( SettingsIds . CachedWindowsExecutableExtensionsSuffixOnly ) ;
32- this . _cachedExes = undefined ;
42+ this . _cachedExes . clear ( ) ;
3343 }
3444 } ) ) ;
3545 }
@@ -41,12 +51,37 @@ export class PathExecutableCache implements vscode.Disposable {
4151 }
4252 }
4353
44- refresh ( ) : void {
45- this . _cachedExes = undefined ;
46- this . _cachedPathValue = undefined ;
54+ refresh ( directory ?: string ) : void {
55+ if ( directory ) {
56+ this . _cachedExes . delete ( directory ) ;
57+ } else {
58+ this . _cachedExes . clear ( ) ;
59+ }
60+ }
61+
62+ async getExecutablesInPath ( env : ITerminalEnvironment = process . env , shellType ?: TerminalShellType ) : Promise < IExecutablesInPath | undefined > {
63+ if ( this . _inProgressRequest &&
64+ this . _inProgressRequest . env === env &&
65+ this . _inProgressRequest . shellType === shellType
66+ ) {
67+ return this . _inProgressRequest . promise ;
68+ }
69+
70+ const promise = this . _doGetExecutablesInPath ( env , shellType ) ;
71+
72+ this . _inProgressRequest = {
73+ env,
74+ shellType,
75+ promise,
76+ } ;
77+
78+ await promise ;
79+ this . _inProgressRequest = undefined ;
80+
81+ return promise ;
4782 }
4883
49- async getExecutablesInPath ( env : ITerminalEnvironment = process . env , shellType ?: TerminalShellType ) : Promise < { completionResources : Set < ICompletionResource > | undefined ; labels : Set < string > | undefined } | undefined > {
84+ private async _doGetExecutablesInPath ( env : ITerminalEnvironment , shellType ?: TerminalShellType ) : Promise < IExecutablesInPath | undefined > {
5085 // Create cache key
5186 let pathValue : string | undefined ;
5287 if ( shellType === TerminalShellType . GitBash ) {
@@ -65,38 +100,59 @@ export class PathExecutableCache implements vscode.Disposable {
65100 return ;
66101 }
67102
68- // Check cache
69- if ( this . _cachedExes && this . _cachedPathValue === pathValue ) {
70- return this . _cachedExes ;
71- }
72-
73103 // Extract executables from PATH
74104 const paths = pathValue . split ( isWindows ? ';' : ':' ) ;
75105 const pathSeparator = isWindows ? '\\' : '/' ;
106+ const promisePaths : string [ ] = [ ] ;
76107 const promises : Promise < Set < ICompletionResource > | undefined > [ ] = [ ] ;
77108 const labels : Set < string > = new Set < string > ( ) ;
78- for ( const path of paths ) {
79- promises . push ( this . _getExecutablesInPath ( path , pathSeparator , labels ) ) ;
109+
110+ for ( const pathDir of paths ) {
111+ // Check if this directory is already cached
112+ const cachedExecutables = this . _cachedExes . get ( pathDir ) ;
113+ if ( cachedExecutables ) {
114+ for ( const executable of cachedExecutables ) {
115+ const labelText = typeof executable . label === 'string' ? executable . label : executable . label . label ;
116+ labels . add ( labelText ) ;
117+ }
118+ } else {
119+ // Not cached, need to scan this directory
120+ promisePaths . push ( pathDir ) ;
121+ promises . push ( this . _getExecutablesInSinglePath ( pathDir , pathSeparator , labels ) ) ;
122+ }
80123 }
81124
82- // Merge all results
125+ // Process uncached directories
126+ if ( promises . length > 0 ) {
127+ const resultSets = await Promise . all ( promises ) ;
128+ for ( const [ i , resultSet ] of resultSets . entries ( ) ) {
129+ const pathDir = promisePaths [ i ] ;
130+ if ( ! this . _cachedExes . has ( pathDir ) ) {
131+ this . _cachedExes . set ( pathDir , resultSet || new Set ( ) ) ;
132+ }
133+ }
134+ }
135+
136+ // Merge all results from all directories
83137 const executables = new Set < ICompletionResource > ( ) ;
84- const resultSets = await Promise . all ( promises ) ;
85- for ( const resultSet of resultSets ) {
86- if ( resultSet ) {
87- for ( const executable of resultSet ) {
138+ const processedPaths : Set < string > = new Set ( ) ;
139+ for ( const pathDir of paths ) {
140+ if ( processedPaths . has ( pathDir ) ) {
141+ continue ;
142+ }
143+ processedPaths . add ( pathDir ) ;
144+ const dirExecutables = this . _cachedExes . get ( pathDir ) ;
145+ if ( dirExecutables ) {
146+ for ( const executable of dirExecutables ) {
88147 executables . add ( executable ) ;
89148 }
90149 }
91150 }
92151
93- // Return
94- this . _cachedPathValue = pathValue ;
95- this . _cachedExes = { completionResources : executables , labels } ;
96- return this . _cachedExes ;
152+ return { completionResources : executables , labels } ;
97153 }
98154
99- private async _getExecutablesInPath ( path : string , pathSeparator : string , labels : Set < string > ) : Promise < Set < ICompletionResource > | undefined > {
155+ private async _getExecutablesInSinglePath ( path : string , pathSeparator : string , labels : Set < string > ) : Promise < Set < ICompletionResource > | undefined > {
100156 try {
101157 const dirExists = await fs . stat ( path ) . then ( stat => stat . isDirectory ( ) ) . catch ( ( ) => false ) ;
102158 if ( ! dirExists ) {
@@ -190,7 +246,7 @@ export async function watchPathDirectories(context: vscode.ExtensionContext, env
190246 const watcher = filesystem . watch ( dir , { persistent : false } , ( ) => {
191247 if ( pathExecutableCache ) {
192248 // Refresh cache when directory contents change
193- pathExecutableCache . refresh ( ) ;
249+ pathExecutableCache . refresh ( dir ) ;
194250 }
195251 } ) ;
196252
0 commit comments