Skip to content

Commit 64bf6c6

Browse files
authored
Merge branch 'main' into tyriar/copilot_fix-258895__259417
2 parents e01ebcb + 3f38070 commit 64bf6c6

File tree

36 files changed

+357
-133
lines changed

36 files changed

+357
-133
lines changed

build/gulpfile.editor.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,8 @@ function toExternalDTS(contents) {
116116
lines[i] = line.replace('declare namespace monaco.', 'export namespace ');
117117
}
118118

119-
if (line.indexOf('declare let MonacoEnvironment') === 0) {
120-
lines[i] = `declare global {\n let MonacoEnvironment: Environment | undefined;\n}`;
121-
}
122-
123-
if (line.indexOf('\tMonacoEnvironment?') === 0) {
124-
lines[i] = ` MonacoEnvironment?: Environment | undefined;`;
119+
if (line.indexOf('declare var MonacoEnvironment') === 0) {
120+
lines[i] = `declare global {\n var MonacoEnvironment: Environment | undefined;\n}`;
125121
}
126122
}
127123
return lines.join('\n').replace(/\n\n\n+/g, '\n\n');

build/monaco/monaco.d.ts.recipe

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
declare let MonacoEnvironment: monaco.Environment | undefined;
7-
8-
interface Window {
9-
MonacoEnvironment?: monaco.Environment | undefined;
10-
}
6+
// eslint-disable-next-line no-var
7+
declare var MonacoEnvironment: monaco.Environment | undefined;
118

129
declare namespace monaco {
1310

extensions/terminal-suggest/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
"terminalCompletionProvider",
1818
"terminalShellEnv"
1919
],
20+
"contributes": {
21+
"commands": [
22+
{
23+
"command": "terminal.integrated.suggest.clearCachedGlobals",
24+
"category": "Terminal",
25+
"title": "%terminal.integrated.suggest.clearCachedGlobals%"
26+
}
27+
]
28+
},
2029
"scripts": {
2130
"compile": "npx gulp compile-extension:terminal-suggest",
2231
"watch": "npx gulp watch-extension:terminal-suggest",
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"description": "Extension to add terminal completions for zsh, bash, and fish terminals.",
33
"displayName": "Terminal Suggest for VS Code",
4-
"view.name": "Terminal Suggest"
4+
"view.name": "Terminal Suggest",
5+
"terminal.integrated.suggest.clearCachedGlobals": "Clear Suggest Cached Globals"
56
}

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

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,30 @@ import { TerminalShellType } from '../terminalSuggestMain';
1616

1717
const isWindows = osIsWindows();
1818

19+
export interface IExecutablesInPath {
20+
completionResources: Set<ICompletionResource> | undefined;
21+
labels: Set<string> | undefined;
22+
}
23+
1924
export 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

extensions/terminal-suggest/src/terminalSuggestMain.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ const getShellSpecificGlobals: Map<TerminalShellType, (options: ExecOptionsWithS
7676
[TerminalShellType.PowerShell, getPwshGlobals],
7777
]);
7878

79-
8079
async function getShellGlobals(
8180
shellType: TerminalShellType,
8281
existingCommands?: Set<string>,
@@ -106,8 +105,11 @@ async function getShellGlobals(
106105
shouldRefresh = true;
107106
}
108107
if (!shouldRefresh && cached.commands) {
109-
// Trigger background refresh
110-
void fetchAndCacheShellGlobals(shellType, existingCommands, machineId, remoteAuthority, true);
108+
// NOTE: This used to trigger a background refresh in order to ensure all commands
109+
// are up to date, but this ends up launching way too many processes. Especially on
110+
// Windows where this caused significant performance issues as processes can block
111+
// the extension host for several seconds
112+
// (https://github.com/microsoft/vscode/issues/259343).
111113
return cached.commands;
112114
}
113115
}
@@ -252,20 +254,15 @@ export async function activate(context: vscode.ExtensionContext) {
252254
return;
253255
}
254256

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-
]);
262-
const shellGlobalsArr = shellGlobals ?? [];
257+
const commandsInPath = await pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env?.value, terminalShellType);
258+
const shellGlobals = await getShellGlobals(terminalShellType, commandsInPath?.labels, machineId, remoteAuthority) ?? [];
259+
263260
if (!commandsInPath?.completionResources) {
264261
console.debug('#terminalCompletions No commands found in path');
265262
return;
266263
}
267264
// Order is important here, add shell globals first so they are prioritized over path commands
268-
const commands = [...shellGlobalsArr, ...commandsInPath.completionResources];
265+
const commands = [...shellGlobals, ...commandsInPath.completionResources];
269266
const currentCommandString = getCurrentCommandAndArgs(terminalContext.commandLine, terminalContext.cursorPosition, terminalShellType);
270267
const pathSeparator = isWindows ? '\\' : '/';
271268
const tokenType = getTokenType(terminalContext, terminalShellType);
@@ -309,6 +306,10 @@ export async function activate(context: vscode.ExtensionContext) {
309306
}
310307
}, '/', '\\'));
311308
await watchPathDirectories(context, currentTerminalEnv, pathExecutableCache);
309+
310+
context.subscriptions.push(vscode.commands.registerCommand('terminal.integrated.suggest.clearCachedGlobals', () => {
311+
cachedGlobals.clear();
312+
}));
312313
}
313314

314315
/**

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 () => {

extensions/xml/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
".rng",
5555
".rss",
5656
".shproj",
57+
".slnx",
5758
".storyboard",
5859
".svg",
5960
".targets",

src/vs/base/browser/ui/dialog/dialog.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@
194194
}
195195

196196
.monaco-dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button {
197-
padding: 5px 10px;
197+
padding: 4px 10px;
198198
overflow: hidden;
199199
text-overflow: ellipsis;
200200
margin: 4px 5px; /* allows button focus outline to be visible */

src/vs/base/common/async.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,24 +2168,12 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> {
21682168
}
21692169
}
21702170

2171-
export class CancelableAsyncIterableObject<T> extends AsyncIterableObject<T> {
2172-
constructor(
2173-
private readonly _source: CancellationTokenSource,
2174-
executor: AsyncIterableExecutor<T>
2175-
) {
2176-
super(executor);
2177-
}
21782171

2179-
cancel(): void {
2180-
this._source.cancel();
2181-
}
2182-
}
2183-
2184-
export function createCancelableAsyncIterable<T>(callback: (token: CancellationToken) => AsyncIterable<T>): CancelableAsyncIterableObject<T> {
2172+
export function createCancelableAsyncIterableProducer<T>(callback: (token: CancellationToken) => AsyncIterable<T>): CancelableAsyncIterableProducer<T> {
21852173
const source = new CancellationTokenSource();
21862174
const innerIterable = callback(source.token);
21872175

2188-
return new CancelableAsyncIterableObject<T>(source, async (emitter) => {
2176+
return new CancelableAsyncIterableProducer<T>(source, async (emitter) => {
21892177
const subscription = source.token.onCancellationRequested(() => {
21902178
subscription.dispose();
21912179
source.dispose();
@@ -2492,6 +2480,19 @@ export class AsyncIterableProducer<T> implements AsyncIterable<T> {
24922480
}
24932481
}
24942482

2483+
export class CancelableAsyncIterableProducer<T> extends AsyncIterableProducer<T> {
2484+
constructor(
2485+
private readonly _source: CancellationTokenSource,
2486+
executor: AsyncIterableExecutor<T>
2487+
) {
2488+
super(executor);
2489+
}
2490+
2491+
cancel(): void {
2492+
this._source.cancel();
2493+
}
2494+
}
2495+
24952496
//#endregion
24962497

24972498
export const AsyncReaderEndOfStream = Symbol('AsyncReaderEndOfStream');

0 commit comments

Comments
 (0)