Skip to content

Commit e151f76

Browse files
authored
Merge pull request microsoft#259347 from microsoft/tyriar/258512__259342
Only refresh cached path results for changes dirs
2 parents d1ad717 + c3380f4 commit e151f76

File tree

3 files changed

+53
-33
lines changed

3 files changed

+53
-33
lines changed

extensions/terminal-suggest/src/env/pathExecutableCache.ts

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,16 @@ const isWindows = osIsWindows();
1919
export class PathExecutableCache implements vscode.Disposable {
2020
private _disposables: vscode.Disposable[] = [];
2121

22-
private _cachedPathValue: string | undefined;
2322
private _cachedWindowsExeExtensions: { [key: string]: boolean | undefined } | undefined;
24-
private _cachedExes: { completionResources: Set<ICompletionResource> | undefined; labels: Set<string> | undefined } | undefined;
23+
private _cachedExes: Map<string, Set<ICompletionResource> | undefined> = new Map();
2524

2625
constructor() {
2726
if (isWindows) {
2827
this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly);
2928
this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => {
3029
if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) {
3130
this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly);
32-
this._cachedExes = undefined;
31+
this._cachedExes.clear();
3332
}
3433
}));
3534
}
@@ -41,9 +40,13 @@ export class PathExecutableCache implements vscode.Disposable {
4140
}
4241
}
4342

44-
refresh(): void {
45-
this._cachedExes = undefined;
46-
this._cachedPathValue = undefined;
43+
refresh(directory?: string): void {
44+
console.trace('clear cache');
45+
if (directory) {
46+
this._cachedExes.delete(directory);
47+
} else {
48+
this._cachedExes.clear();
49+
}
4750
}
4851

4952
async getExecutablesInPath(env: ITerminalEnvironment = process.env, shellType?: TerminalShellType): Promise<{ completionResources: Set<ICompletionResource> | undefined; labels: Set<string> | undefined } | undefined> {
@@ -65,35 +68,56 @@ export class PathExecutableCache implements vscode.Disposable {
6568
return;
6669
}
6770

68-
// Check cache
69-
if (this._cachedExes && this._cachedPathValue === pathValue) {
70-
return this._cachedExes;
71-
}
72-
7371
// Extract executables from PATH
7472
const paths = pathValue.split(isWindows ? ';' : ':');
7573
const pathSeparator = isWindows ? '\\' : '/';
74+
const promisePaths: string[] = [];
7675
const promises: Promise<Set<ICompletionResource> | undefined>[] = [];
7776
const labels: Set<string> = new Set<string>();
78-
for (const path of paths) {
79-
promises.push(this._getExecutablesInPath(path, pathSeparator, labels));
77+
78+
for (const pathDir of paths) {
79+
// Check if this directory is already cached
80+
const cachedExecutables = this._cachedExes.get(pathDir);
81+
if (cachedExecutables) {
82+
for (const executable of cachedExecutables) {
83+
const labelText = typeof executable.label === 'string' ? executable.label : executable.label.label;
84+
labels.add(labelText);
85+
}
86+
} else {
87+
// Not cached, need to scan this directory
88+
promisePaths.push(pathDir);
89+
promises.push(this._getExecutablesInPath(pathDir, pathSeparator, labels));
90+
}
8091
}
8192

82-
// Merge all results
93+
// Process uncached directories
94+
if (promises.length > 0) {
95+
const resultSets = await Promise.all(promises);
96+
for (const [i, resultSet] of resultSets.entries()) {
97+
const pathDir = promisePaths[i];
98+
if (!this._cachedExes.has(pathDir)) {
99+
this._cachedExes.set(pathDir, resultSet || new Set());
100+
}
101+
}
102+
}
103+
104+
// Merge all results from all directories
83105
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) {
106+
const processedPaths: Set<string> = new Set();
107+
for (const pathDir of paths) {
108+
if (processedPaths.has(pathDir)) {
109+
continue;
110+
}
111+
processedPaths.add(pathDir);
112+
const dirExecutables = this._cachedExes.get(pathDir);
113+
if (dirExecutables) {
114+
for (const executable of dirExecutables) {
88115
executables.add(executable);
89116
}
90117
}
91118
}
92119

93-
// Return
94-
this._cachedPathValue = pathValue;
95-
this._cachedExes = { completionResources: executables, labels };
96-
return this._cachedExes;
120+
return { completionResources: executables, labels };
97121
}
98122

99123
private async _getExecutablesInPath(path: string, pathSeparator: string, labels: Set<string>): Promise<Set<ICompletionResource> | undefined> {
@@ -190,7 +214,7 @@ export async function watchPathDirectories(context: vscode.ExtensionContext, env
190214
const watcher = filesystem.watch(dir, { persistent: false }, () => {
191215
if (pathExecutableCache) {
192216
// Refresh cache when directory contents change
193-
pathExecutableCache.refresh();
217+
pathExecutableCache.refresh(dir);
194218
}
195219
});
196220

extensions/terminal-suggest/src/terminalSuggestMain.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,9 @@ export async function activate(context: vscode.ExtensionContext) {
252252
return;
253253
}
254254

255-
const [commandsInPath, shellGlobals] = await Promise.all([
256-
pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env?.value, terminalShellType),
257-
(async () => {
258-
const executables = await pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env?.value, terminalShellType);
259-
return getShellGlobals(terminalShellType, executables?.labels, machineId, remoteAuthority);
260-
})()
261-
]);
255+
const commandsInPath = await pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env?.value, terminalShellType);
256+
const shellGlobals = await getShellGlobals(terminalShellType, commandsInPath?.labels, machineId, remoteAuthority);
257+
262258
const shellGlobalsArr = shellGlobals ?? [];
263259
if (!commandsInPath?.completionResources) {
264260
console.debug('#terminalCompletions No commands found in path');

extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import 'mocha';
7-
import { strictEqual } from 'node:assert';
7+
import { deepStrictEqual, strictEqual } from 'node:assert';
88
import type { MarkdownString } from 'vscode';
99
import { PathExecutableCache } from '../../env/pathExecutableCache';
1010

@@ -16,12 +16,12 @@ suite('PathExecutableCache', () => {
1616
strictEqual(Array.from(result!.labels!).length, 0);
1717
});
1818

19-
test('caching is working on successive calls', async () => {
19+
test('results are the same on successive calls', async () => {
2020
const cache = new PathExecutableCache();
2121
const env = { PATH: process.env.PATH };
2222
const result = await cache.getExecutablesInPath(env);
2323
const result2 = await cache.getExecutablesInPath(env);
24-
strictEqual(result, result2);
24+
deepStrictEqual(result!.labels, result2!.labels);
2525
});
2626

2727
test('refresh clears the cache', async () => {

0 commit comments

Comments
 (0)