From a8ca5380a4372eb9366854830b2b263f9b9055f6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Feb 2023 14:48:07 +0100 Subject: [PATCH 01/85] shared process - use `isMainRequestServiceEnabled` by default --- .../code/electron-browser/sharedProcess/sharedProcessMain.ts | 2 +- .../electron-browser/sharedProcessLifecycleService.ts | 2 +- .../request/electron-browser/sharedProcessRequestService.ts | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index b158b4299b4..ebf2ba99577 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -272,7 +272,7 @@ class SharedProcessMain extends Disposable { services.set(IUriIdentityService, uriIdentityService); // Request - services.set(IRequestService, new SharedProcessRequestService(mainProcessService, configurationService, productService, logService)); + services.set(IRequestService, new SharedProcessRequestService(mainProcessService, configurationService, logService)); // Checksum services.set(IChecksumService, new SyncDescriptor(ChecksumService, undefined, false /* proxied to other processes */)); diff --git a/src/vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService.ts b/src/vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService.ts index a23e4b912a6..dfe975ec19f 100644 --- a/src/vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService.ts +++ b/src/vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -export const ISharedProcessLifecycleService = createDecorator('lifecycleSharedProcessService'); +export const ISharedProcessLifecycleService = createDecorator('sharedProcessLifecycleService'); export interface ISharedProcessLifecycleService { readonly _serviceBrand: undefined; diff --git a/src/vs/platform/request/electron-browser/sharedProcessRequestService.ts b/src/vs/platform/request/electron-browser/sharedProcessRequestService.ts index 9af32dd592d..4a286e4eaf2 100644 --- a/src/vs/platform/request/electron-browser/sharedProcessRequestService.ts +++ b/src/vs/platform/request/electron-browser/sharedProcessRequestService.ts @@ -9,7 +9,6 @@ import { IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/r import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProductService } from 'vs/platform/product/common/productService'; import { RequestService } from 'vs/platform/request/browser/requestService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestChannelClient } from 'vs/platform/request/common/requestIpc'; @@ -24,7 +23,6 @@ export class SharedProcessRequestService implements IRequestService { constructor( mainProcessService: IMainProcessService, private readonly configurationService: IConfigurationService, - private readonly productService: IProductService, private readonly logService: ILogService, ) { this.browserRequestService = new RequestService(configurationService, logService); @@ -53,6 +51,6 @@ export class SharedProcessRequestService implements IRequestService { if (isBoolean(value)) { return value; } - return this.productService.quality !== 'stable'; + return true; } } From 4ab50c858ad6973d4b29a15b9c45812fcb92f3d1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Feb 2023 14:55:51 +0100 Subject: [PATCH 02/85] shared process - add experimental setting --- src/vs/platform/window/common/window.ts | 2 +- .../platform/windows/electron-main/windowsMainService.ts | 2 +- .../contrib/relauncher/browser/relauncher.contribution.ts | 7 ++++++- src/vs/workbench/electron-sandbox/desktop.contribution.ts | 7 +++++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index ccb87376d87..bd27b2c1fc0 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -137,7 +137,7 @@ export interface IWindowSettings { readonly enableMenuBarMnemonics: boolean; readonly closeWhenEmpty: boolean; readonly clickThroughInactive: boolean; - readonly experimental?: { useSandbox: boolean }; + readonly experimental?: { useSandbox: boolean; sharedProcessUseUtilityProcess: boolean }; } export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index b2282599855..b87505e4eed 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1388,7 +1388,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic policiesData: this.policyService.serialize(), continueOn: this.environmentMainService.continueOn, - preferUtilityProcess: filesConfig?.experimental?.watcherUseUtilityProcess ?? false + preferUtilityProcess: filesConfig?.experimental?.watcherUseUtilityProcess ?? windowConfig?.experimental?.sharedProcessUseUtilityProcess ?? false }; // New window diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index a62d0b3c7b7..45c9bd63d79 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -27,7 +27,7 @@ interface IConfiguration extends IWindowsConfiguration { debug?: { console?: { wordWrap?: boolean } }; editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' }; security?: { workspace?: { trust?: { enabled?: boolean } } }; - window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean }; useSandbox?: boolean } }; + window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean }; useSandbox?: boolean; sharedProcessUseUtilityProcess?: boolean } }; workbench?: { enableExperiments?: boolean }; _extensionsGallery?: { enablePPE?: boolean }; } @@ -38,6 +38,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo 'window.titleBarStyle', 'window.experimental.windowControlsOverlay.enabled', 'window.experimental.useSandbox', + 'window.experimental.sharedProcessUseUtilityProcess', 'files.experimental.watcherUseUtilityProcess', 'window.nativeTabs', 'window.nativeFullScreen', @@ -53,6 +54,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private readonly windowControlsOverlayEnabled = new ChangeObserver('boolean'); private readonly windowSandboxEnabled = new ChangeObserver('boolean'); private readonly fileWatcherUtilityProcessEnabled = new ChangeObserver('boolean'); + private readonly sharedProcessUtilityProcessEnabled = new ChangeObserver('boolean'); private readonly nativeTabs = new ChangeObserver('boolean'); private readonly nativeFullScreen = new ChangeObserver('boolean'); private readonly clickThroughInactive = new ChangeObserver('boolean'); @@ -101,6 +103,9 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo // File Watcher: Utility Process processChanged(this.fileWatcherUtilityProcessEnabled.handleChange(config.files?.experimental?.watcherUseUtilityProcess)); + // Shared Process: Utility Process + processChanged(this.sharedProcessUtilityProcessEnabled.handleChange(config.window?.experimental?.sharedProcessUseUtilityProcess)); + // macOS: Native tabs processChanged(isMacintosh && this.nativeTabs.handleChange(config.window?.nativeTabs)); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 1b9d1d905a9..1c7aa6f610a 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -267,6 +267,13 @@ import { applicationConfigurationNodeBase } from 'vs/workbench/common/configurat 'scope': ConfigurationScope.APPLICATION, ignoreSync: true }, + 'window.experimental.sharedProcessUseUtilityProcess': { // TODO@bpasero remove me once sandbox is final + type: 'boolean', + description: localize('experimentalUseSharedProcessUseUtilityProcess', "Experimental: When enabled, the window will have sandbox mode enabled via Electron API."), + default: false, //typeof product.quality === 'string' && product.quality !== 'stable', // disabled by default in stable for now + 'scope': ConfigurationScope.APPLICATION, + ignoreSync: true + } } }); From b4acefbcb3338460877b653e0093f01d4483c8ad Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 14 Feb 2023 16:21:49 +0100 Subject: [PATCH 03/85] remove unused `strict` option from context-keys deserialization (#174349) Co-authored-by: Ulugbek Abdullaev --- .../platform/contextkey/common/contextkey.ts | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 111cf4bce1c..87d054af797 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -124,50 +124,50 @@ export abstract class ContextKeyExpr { return ContextKeySmallerEqualsExpr.create(key, value); } - public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpression | undefined { + public static deserialize(serialized: string | null | undefined): ContextKeyExpression | undefined { if (!serialized) { return undefined; } - return this._deserializeOrExpression(serialized, strict); + return this._deserializeOrExpression(serialized); } - private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpression | undefined { + private static _deserializeOrExpression(serialized: string): ContextKeyExpression | undefined { const pieces = serialized.split('||'); - return ContextKeyOrExpr.create(pieces.map(p => this._deserializeAndExpression(p, strict)), null, true); + return ContextKeyOrExpr.create(pieces.map(p => this._deserializeAndExpression(p)), null, true); } - private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpression | undefined { + private static _deserializeAndExpression(serialized: string): ContextKeyExpression | undefined { const pieces = serialized.split('&&'); - return ContextKeyAndExpr.create(pieces.map(p => this._deserializeOne(p, strict)), null, true); + return ContextKeyAndExpr.create(pieces.map(p => this._deserializeOne(p)), null, true); } - private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpression { + private static _deserializeOne(serializedOne: string): ContextKeyExpression { serializedOne = serializedOne.trim(); if (serializedOne.indexOf('!=') >= 0) { const pieces = serializedOne.split('!='); - return ContextKeyNotEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyNotEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1])); } if (serializedOne.indexOf('==') >= 0) { const pieces = serializedOne.split('=='); - return ContextKeyEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1])); } if (serializedOne.indexOf('=~') >= 0) { const pieces = serializedOne.split('=~'); - return ContextKeyRegexExpr.create(pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict)); + return ContextKeyRegexExpr.create(pieces[0].trim(), this._deserializeRegexValue(pieces[1])); } - if (serializedOne.indexOf(' not in ') >= 0) { + if (serializedOne.indexOf(' not in ') >= 0) { // careful: this must come before `in` const pieces = serializedOne.split(' not in '); - return ContextKeyNotInExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyNotInExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1])); } if (serializedOne.indexOf(' in ') >= 0) { const pieces = serializedOne.split(' in '); - return ContextKeyInExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyInExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1])); } if (/^[^<=>]+>=[^<=>]+$/.test(serializedOne)) { @@ -197,7 +197,7 @@ export abstract class ContextKeyExpr { return ContextKeyDefinedExpr.create(serializedOne); } - private static _deserializeValue(serializedValue: string, strict: boolean): any { + private static _deserializeValue(serializedValue: string): any { serializedValue = serializedValue.trim(); if (serializedValue === 'true') { @@ -216,25 +216,17 @@ export abstract class ContextKeyExpr { return serializedValue; } - private static _deserializeRegexValue(serializedValue: string, strict: boolean): RegExp | null { + private static _deserializeRegexValue(serializedValue: string): RegExp | null { if (isFalsyOrWhitespace(serializedValue)) { - if (strict) { - throw new Error('missing regexp-value for =~-expression'); - } else { - console.warn('missing regexp-value for =~-expression'); - } + console.warn('missing regexp-value for =~-expression'); return null; } const start = serializedValue.indexOf('/'); const end = serializedValue.lastIndexOf('/'); if (start === end || start < 0 /* || to < 0 */) { - if (strict) { - throw new Error(`bad regexp-value '${serializedValue}', missing /-enclosure`); - } else { - console.warn(`bad regexp-value '${serializedValue}', missing /-enclosure`); - } + console.warn(`bad regexp-value '${serializedValue}', missing /-enclosure`); return null; } @@ -243,11 +235,7 @@ export abstract class ContextKeyExpr { try { return new RegExp(value, caseIgnoreFlag); } catch (e) { - if (strict) { - throw new Error(`bad regexp-value '${serializedValue}', parse error: ${e}`); - } else { - console.warn(`bad regexp-value '${serializedValue}', parse error: ${e}`); - } + console.warn(`bad regexp-value '${serializedValue}', parse error: ${e}`); return null; } } From bd709c754c12097a53bb39c411aa920429ebf47b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 14 Feb 2023 07:40:56 -0800 Subject: [PATCH 04/85] Improve IHoverOptions.container docs --- src/vs/workbench/services/hover/browser/hover.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index d5b60af6262..d08a57d9fb1 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -7,6 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; export const IHoverService = createDecorator('hoverService'); @@ -128,7 +129,10 @@ export interface IHoverOptions { trapFocus?: boolean; /** - * The container to render the hover in. + * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover + * in. This is particularly useful for more natural tab focusing behavior, where the hover is + * created as the next tab index after the element being hovered and/or to workaround the + * element's container hiding on `focusout`. */ container?: HTMLElement; } From 9076750961bc7c95db1de46f79c6d25e3b254359 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 16:49:45 +0100 Subject: [PATCH 05/85] Bump http-cache-semantics from 4.1.0 to 4.1.1 in /build/npm/gyp (#173400) Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: http-cache-semantics dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/npm/gyp/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/npm/gyp/yarn.lock b/build/npm/gyp/yarn.lock index ed79f4868b7..6b1ba220c7e 100644 --- a/build/npm/gyp/yarn.lock +++ b/build/npm/gyp/yarn.lock @@ -226,9 +226,9 @@ has-unicode@^2.0.1: integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= http-cache-semantics@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== http-proxy-agent@^4.0.1: version "4.0.1" From 07a7d710872e1d7ffc0a9863c9a6cb441ac445c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 14 Feb 2023 16:53:34 +0100 Subject: [PATCH 06/85] Bump to @vscode/policy-watcher (#174354) * use latest @vscode/policy-watcher * fix compile * ooops * lock new version --- .eslintrc.json | 2 +- build/.moduleignore | 14 +++++----- package.json | 2 +- .../policy/node/nativePolicyService.ts | 2 +- yarn.lock | 26 +++++++++---------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3b8d12542e5..d95ee4630c3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -227,6 +227,7 @@ "@vscode/vscode-languagedetection", "@vscode/ripgrep", "@vscode/iconv-lite-umd", + "@vscode/policy-watcher", "assert", "child_process", "console", @@ -254,7 +255,6 @@ "url", "util", "v8-inspect-profiler", - "vscode-policy-watcher", "vscode-proxy-agent", "vscode-regexpp", "vscode-textmate", diff --git a/build/.moduleignore b/build/.moduleignore index e2b045acc34..7b57188f137 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -113,13 +113,13 @@ vscode-encrypt/binding.gyp vscode-encrypt/README.md !vscode-encrypt/build/Release/vscode-encrypt-native.node -vscode-policy-watcher/build/** -vscode-policy-watcher/.husky/** -vscode-policy-watcher/src/** -vscode-policy-watcher/binding.gyp -vscode-policy-watcher/README.md -vscode-policy-watcher/index.d.ts -!vscode-policy-watcher/build/Release/vscode-policy-watcher.node +@vscode/policy-watcher/build/** +@vscode/policy-watcher/.husky/** +@vscode/policy-watcher/src/** +@vscode/policy-watcher/binding.gyp +@vscode/policy-watcher/README.md +@vscode/policy-watcher/index.d.ts +!@vscode/policy-watcher/build/Release/vscode-policy-watcher.node vscode-windows-ca-certs/**/* !vscode-windows-ca-certs/package.json diff --git a/package.json b/package.json index d0f11fef289..d3bef22f362 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@microsoft/1ds-post-js": "^3.2.2", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/policy-watcher": "^1.1.4", "@vscode/ripgrep": "^1.14.2", "@vscode/sqlite3": "5.1.2-vscode", "@vscode/sudo-prompt": "9.3.1", @@ -83,7 +84,6 @@ "tas-client-umd": "0.1.6", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.7.0", - "vscode-policy-watcher": "^1.1.1", "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "8.0.0", diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 7b78806548f..eae3bf8dfac 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -6,7 +6,7 @@ import { AbstractPolicyService, IPolicyService, PolicyDefinition } from 'vs/platform/policy/common/policy'; import { IStringDictionary } from 'vs/base/common/collections'; import { Throttler } from 'vs/base/common/async'; -import { createWatcher, PolicyUpdate, Watcher } from 'vscode-policy-watcher'; +import { createWatcher, PolicyUpdate, Watcher } from '@vscode/policy-watcher'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/yarn.lock b/yarn.lock index cdf37e12fb9..55375683415 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1233,6 +1233,14 @@ xml2js "^0.4.23" yargs "^17.5.1" +"@vscode/policy-watcher@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@vscode/policy-watcher/-/policy-watcher-1.1.4.tgz#a47e30c6f02d025187d8900a3e21adb2626dece5" + integrity sha512-/xYsB7PmEeEpuRIKPAqDvK8baJ6AfOhHsnPPJrje7Bpf1z2GLwRFq7pm9KjBGtcwJGInuDxQERMtX0RIERi8YA== + dependencies: + bindings "^1.5.0" + node-addon-api "^6.0.0" + "@vscode/ripgrep@^1.14.2": version "1.14.2" resolved "https://registry.yarnpkg.com/@vscode/ripgrep/-/ripgrep-1.14.2.tgz#47c0eec2b64f53d8f7e1b5ffd22a62e229191c34" @@ -7752,11 +7760,6 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-addon-api@*: - version "5.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" - integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== - node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" @@ -7777,6 +7780,11 @@ node-addon-api@^4.3.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.0.0.tgz#cfb3574e6df708ff71a30db6c4762d9e06e11c27" + integrity sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA== + node-fetch@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" @@ -11425,14 +11433,6 @@ vscode-oniguruma@1.7.0: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== -vscode-policy-watcher@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.1.1.tgz#3646d17de9f82c741437fd6d687fd0c414ea513d" - integrity sha512-ZwJKg8gpIQ3UO8gdTqBvbXJBOBUStWPArjQJuqGIz8TimgMy+4H7tScVjvlxmzb/sS6rErn3wGMn8gRH824nnw== - dependencies: - bindings "^1.5.0" - node-addon-api "*" - vscode-proxy-agent@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.12.0.tgz#0775f464b9519b0c903da4dcf50851e1453f4e48" From d619dcfc77328042e045b87e30808f5c8426943b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 14 Feb 2023 17:21:39 +0100 Subject: [PATCH 07/85] bump build/npm/gyp deps (#174361) --- build/npm/gyp/package.json | 3 +- build/npm/gyp/yarn.lock | 202 +++++++++++++++++++------------------ 2 files changed, 104 insertions(+), 101 deletions(-) diff --git a/build/npm/gyp/package.json b/build/npm/gyp/package.json index 9efc7b78788..0efaa499b9d 100644 --- a/build/npm/gyp/package.json +++ b/build/npm/gyp/package.json @@ -6,6 +6,5 @@ "devDependencies": { "node-gyp": "^8.4.1" }, - "scripts": { - } + "scripts": {} } diff --git a/build/npm/gyp/yarn.lock b/build/npm/gyp/yarn.lock index 6b1ba220c7e..d5d6bced114 100644 --- a/build/npm/gyp/yarn.lock +++ b/build/npm/gyp/yarn.lock @@ -3,14 +3,14 @@ "@gar/promisify@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" - integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== "@npmcli/fs@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" - integrity sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== dependencies: "@gar/promisify" "^1.0.1" semver "^7.3.5" @@ -41,9 +41,9 @@ agent-base@6, agent-base@^6.0.2: debug "4" agentkeepalive@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" - integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== dependencies: debug "^4.1.0" depd "^1.1.2" @@ -67,10 +67,10 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -are-we-there-yet@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" - integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== dependencies: delegates "^1.0.0" readable-stream "^3.6.0" @@ -122,7 +122,7 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -color-support@^1.1.2: +color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -130,29 +130,29 @@ color-support@^1.1.2: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -console-control-strings@^1.0.0, console-control-strings@^1.1.0: +console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -debug@4, debug@^4.1.0, debug@^4.3.1: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4, debug@^4.1.0, debug@^4.3.3: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== depd@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== emoji-regex@^8.0.0: version "8.0.0" @@ -186,44 +186,43 @@ fs-minipass@^2.0.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -gauge@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.0.tgz#afba07aa0374a93c6219603b1fb83eaa2264d8f8" - integrity sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw== +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== dependencies: - ansi-regex "^5.0.1" aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.2" - console-control-strings "^1.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" has-unicode "^2.0.1" - signal-exit "^3.0.0" + signal-exit "^3.0.7" string-width "^4.2.3" strip-ansi "^6.0.1" - wide-align "^1.1.2" + wide-align "^1.1.5" glob@^7.1.3, glob@^7.1.4: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" graceful-fs@^4.2.6: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== http-cache-semantics@^4.1.0: version "4.1.1" @@ -240,9 +239,9 @@ http-proxy-agent@^4.0.1: debug "4" https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" @@ -250,7 +249,7 @@ https-proxy-agent@^5.0.0: humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== dependencies: ms "^2.0.0" @@ -264,7 +263,7 @@ iconv-lite@^0.6.2: imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" @@ -279,7 +278,7 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -289,10 +288,10 @@ inherits@2, inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -302,12 +301,12 @@ is-fullwidth-code-point@^3.0.0: is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== lru-cache@^6.0.0: version "6.0.0" @@ -338,10 +337,10 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" @@ -385,12 +384,17 @@ minipass-sized@^1.0.3: minipass "^3.0.0" minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" - integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" +minipass@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.3.tgz#00bfbaf1e16e35e804f4aa31a7c1f6b8d9f0ee72" + integrity sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw== + minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -415,9 +419,9 @@ ms@^2.0.0: integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== negotiator@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== node-gyp@^8.4.1: version "8.4.1" @@ -443,19 +447,19 @@ nopt@^5.0.0: abbrev "1" npmlog@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.0.tgz#ba9ef39413c3d936ea91553db7be49c34ad0520c" - integrity sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q== + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== dependencies: - are-we-there-yet "^2.0.0" + are-we-there-yet "^3.0.0" console-control-strings "^1.1.0" - gauge "^4.0.0" + gauge "^4.0.3" set-blocking "^2.0.0" once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" @@ -469,12 +473,12 @@ p-map@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== promise-retry@^2.0.1: version "2.0.1" @@ -496,7 +500,7 @@ readable-stream@^3.6.0: retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== rimraf@^3.0.2: version "3.0.2" @@ -516,43 +520,43 @@ safe-buffer@~5.2.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -signal-exit@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== +signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -smart-buffer@^4.1.0: +smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== socks-proxy-agent@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" - integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== dependencies: agent-base "^6.0.2" - debug "^4.3.1" - socks "^2.6.1" + debug "^4.3.3" + socks "^2.6.2" -socks@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" - integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== +socks@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: - ip "^1.1.5" - smart-buffer "^4.1.0" + ip "^2.0.0" + smart-buffer "^4.2.0" ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" @@ -585,13 +589,13 @@ strip-ansi@^6.0.1: ansi-regex "^5.0.1" tar@^6.0.2, tar@^6.1.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^3.0.0" + minipass "^4.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -613,7 +617,7 @@ unique-slug@^2.0.0: util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== which@^2.0.2: version "2.0.2" @@ -622,7 +626,7 @@ which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.2: +wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== @@ -632,7 +636,7 @@ wide-align@^1.1.2: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== yallist@^4.0.0: version "4.0.0" From 9d8b738122e25eaa1f562675b2b709d67ab18fe3 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 14 Feb 2023 10:43:27 -0800 Subject: [PATCH 08/85] Fix microsoft/vscode-jupyter#12842. Add output text word wrap setting. (#174384) --- extensions/notebook-renderers/src/index.ts | 21 +++++++++++++++++++ .../notebook/browser/notebook.contribution.ts | 8 ++++++- .../notebook/browser/notebookOptions.ts | 5 +++++ .../view/renderers/backLayerWebView.ts | 4 +++- .../browser/view/renderers/webviewPreloads.ts | 3 +++ .../contrib/notebook/common/notebookCommon.ts | 1 + 6 files changed, 40 insertions(+), 2 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 332faeb0226..f24776d1624 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -31,6 +31,7 @@ interface JavaScriptRenderingHook { interface RenderOptions { readonly lineLimit: number; readonly outputScrolling: boolean; + readonly outputWordWrap: boolean; } function clearContainer(container: HTMLElement) { @@ -149,6 +150,9 @@ function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: Render if (err.stack) { const stack = document.createElement('pre'); stack.classList.add('traceback'); + if (ctx.settings.outputWordWrap) { + stack.classList.add('wordWrap'); + } stack.style.margin = '8px 0'; const element = document.createElement('span'); insertOutput(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, false, element, true); @@ -190,6 +194,11 @@ function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boo const text = outputInfo.text(); const element = existing ?? document.createElement('span'); element.classList.add('output-stream'); + if (ctx.settings.outputWordWrap) { + element.classList.add('wordWrap'); + } else { + element.classList.remove('wordWrap'); + } element.setAttribute('output-item-id', outputInfo.id); insertOutput(outputInfo.id, [text], ctx.settings.lineLimit, ctx.settings.outputScrolling, element, false); outputElement.appendChild(element); @@ -199,6 +208,9 @@ function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boo const element = document.createElement('span'); element.classList.add('output-stream'); + if (ctx.settings.outputWordWrap) { + element.classList.add('wordWrap'); + } element.setAttribute('output-item-id', outputInfo.id); const text = outputInfo.text(); @@ -217,6 +229,9 @@ function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: Rendere clearContainer(container); const contentNode = document.createElement('div'); contentNode.classList.add('output-plaintext'); + if (ctx.settings.outputWordWrap) { + contentNode.classList.add('wordWrap'); + } const text = outputInfo.text(); insertOutput(outputInfo.id, [text], ctx.settings.lineLimit, ctx.settings.outputScrolling, contentNode, false); container.appendChild(contentNode); @@ -244,6 +259,12 @@ export const activate: ActivationFunction = (ctx) => { -ms-user-select: text; cursor: auto; } + .output-plaintext.wordWrap span, + .output-stream.wordWrap span, + .traceback.wordWrap span { + white-space: pre-wrap !important; + word-break: break-all; + } .output-plaintext, .output-stream { white-space: pre-wrap; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 238b5e2880c..6ea12228fbc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -892,7 +892,13 @@ configurationRegistry.registerConfiguration({ type: 'boolean', tags: ['notebookLayout'], default: false - } + }, + [NotebookSetting.outputWordWrap]: { + markdownDescription: nls.localize('notebook.outputWordWrap', "Controls whether the lines in output should wrap."), + type: 'boolean', + tags: ['notebookLayout'], + default: false + }, } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 3df4dcbe2b7..e9d1ef61314 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -75,6 +75,7 @@ export interface NotebookLayoutConfiguration { focusIndicatorGap: number; interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells; outputScrolling: boolean; + outputWordWrap: boolean; outputLineLimit: number; } @@ -154,6 +155,7 @@ export class NotebookOptions extends Disposable { const interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells = this.configurationService.getValue(NotebookSetting.interactiveWindowCollapseCodeCells); const outputLineHeight = this._computeOutputLineHeight(); const outputScrolling = this.configurationService.getValue(NotebookSetting.outputScrolling); + const outputWordWrap = this.configurationService.getValue(NotebookSetting.outputWordWrap); const outputLineLimit = this.configurationService.getValue(NotebookSetting.textOutputLineLimit) ?? 30; this._layoutConfiguration = { @@ -193,6 +195,7 @@ export class NotebookOptions extends Disposable { interactiveWindowCollapseCodeCells, markdownFoldHintHeight: 22, outputScrolling: outputScrolling, + outputWordWrap: outputWordWrap, outputLineLimit: outputLineLimit }; @@ -582,6 +585,7 @@ export class NotebookOptions extends Disposable { markupFontSize: this._layoutConfiguration.markupFontSize, outputLineHeight: this._layoutConfiguration.outputLineHeight, outputScrolling: this._layoutConfiguration.outputScrolling, + outputWordWrap: this._layoutConfiguration.outputWordWrap, outputLineLimit: this._layoutConfiguration.outputLineLimit, }; } @@ -602,6 +606,7 @@ export class NotebookOptions extends Disposable { markupFontSize: this._layoutConfiguration.markupFontSize, outputLineHeight: this._layoutConfiguration.outputLineHeight, outputScrolling: this._layoutConfiguration.outputScrolling, + outputWordWrap: this._layoutConfiguration.outputWordWrap, outputLineLimit: this._layoutConfiguration.outputLineLimit, }; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index bfbb1132eaf..cac752e9192 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -107,6 +107,7 @@ interface BacklayerWebviewOptions { readonly markupFontSize: number; readonly outputLineHeight: number; readonly outputScrolling: boolean; + readonly outputWordWrap: boolean; readonly outputLineLimit: number; } @@ -264,7 +265,8 @@ export class BackLayerWebView extends Themable { const preloadsData = this.getStaticPreloadsData(); const renderOptions = { lineLimit: this.options.outputLineLimit, - outputScrolling: this.options.outputScrolling + outputScrolling: this.options.outputScrolling, + outputWordWrap: this.options.outputWordWrap }; const preloadScript = preloadsScriptStr( this.options, diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 485b6f262cc..50dd53e28aa 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -67,6 +67,7 @@ export interface PreloadOptions { export interface RenderOptions { readonly lineLimit: number; readonly outputScrolling: boolean; + readonly outputWordWrap: boolean; } interface PreloadContext { @@ -89,6 +90,7 @@ async function webviewPreloads(ctx: PreloadContext) { let isWorkspaceTrusted = ctx.isWorkspaceTrusted; const lineLimit = ctx.renderOptions.lineLimit; const outputScrolling = ctx.renderOptions.outputScrolling; + const outputWordWrap = ctx.renderOptions.outputWordWrap; const acquireVsCodeApi = globalThis.acquireVsCodeApi; const vscode = acquireVsCodeApi(); @@ -1353,6 +1355,7 @@ async function webviewPreloads(ctx: PreloadContext) { settings: { get lineLimit() { return lineLimit; }, get outputScrolling() { return outputScrolling; }, + get outputWordWrap() { return outputWordWrap; }, } }; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 3c349b700fc..1cc134ca882 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -929,6 +929,7 @@ export const NotebookSetting = { outputFontFamily: 'notebook.outputFontFamily', kernelPickerType: 'notebook.kernelPicker.type', outputScrolling: 'notebook.experimental.outputScrolling', + outputWordWrap: 'notebook.output.wordWrap', logging: 'notebook.logging', } as const; From 7c2b2aa67e4e00ff1ebc24f3f28c179a8d304f6e Mon Sep 17 00:00:00 2001 From: aamunger Date: Tue, 14 Feb 2023 10:43:27 -0800 Subject: [PATCH 09/85] allow non-api command instead --- .../contrib/notebook/browser/view/renderers/backLayerWebView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index bfbb1132eaf..d839497f19e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -675,7 +675,7 @@ export class BackLayerWebView extends Themable { 'github-issues.authNow', 'workbench.extensions.search', 'workbench.action.openSettings', - 'notebook.selectKernel', + '_notebook.selectKernel', ], }); return; From 3303afad3f46b76ea3e65ed954650f8eec73b559 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Feb 2023 20:06:45 +0100 Subject: [PATCH 10/85] sandbox - add sandbox info to utility process crashes (#174390) --- .../electron-main/utilityProcess.ts | 24 ++++++++++++------- .../platform/window/electron-main/window.ts | 2 ++ .../windows/electron-main/windowImpl.ts | 5 ++++ .../test/electron-main/windowsFinder.test.ts | 1 + 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index bd5d3f7d7b2..59886b3ac7c 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -15,6 +15,7 @@ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import Severity from 'vs/base/common/severity'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { ICodeWindow } from 'vs/platform/window/electron-main/window'; export interface IUtilityProcessConfiguration { @@ -150,7 +151,7 @@ export class UtilityProcess extends Disposable { } } - private validateCanStart(configuration: IUtilityProcessConfiguration): BrowserWindow | undefined { + private validateCanStart(configuration: IUtilityProcessConfiguration): ICodeWindow | undefined { if (!canUseUtilityProcess) { throw new Error('Cannot use UtilityProcess API from Electron!'); } @@ -160,8 +161,8 @@ export class UtilityProcess extends Disposable { return undefined; } - const responseWindow = this.windowsMainService.getWindowById(configuration.responseWindowId)?.win; - if (!responseWindow || responseWindow.isDestroyed() || responseWindow.webContents.isDestroyed()) { + const responseWindow = this.windowsMainService.getWindowById(configuration.responseWindowId); + if (!responseWindow?.win || responseWindow.win.isDestroyed() || responseWindow.win.webContents.isDestroyed()) { this.log('Refusing to start utility process because requesting window cannot be found or is destroyed...', Severity.Error); return undefined; } @@ -171,7 +172,7 @@ export class UtilityProcess extends Disposable { start(configuration: IUtilityProcessConfiguration): boolean { const responseWindow = this.validateCanStart(configuration); - if (!responseWindow) { + if (!responseWindow?.win) { return false; } @@ -205,15 +206,15 @@ export class UtilityProcess extends Disposable { }); // Register to events - this.registerListeners(responseWindow, this.process, this.configuration, serviceName); + this.registerListeners(responseWindow.win, this.process, this.configuration, serviceName, responseWindow.isSandboxed); // Exchange message ports - this.exchangeMessagePorts(this.process, this.configuration, responseWindow); + this.exchangeMessagePorts(this.process, this.configuration, responseWindow.win); return true; } - private registerListeners(window: BrowserWindow, process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { + private registerListeners(window: BrowserWindow, process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string, isWindowSandboxed: boolean): void { // If the lifecycle of the utility process is bound to the window, // we kill the process if the window closes or changes @@ -264,6 +265,7 @@ export class UtilityProcess extends Disposable { type UtilityProcessCrashClassification = { type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The type of utility process to understand the origin of the crash better.' }; reason: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reason of the utility process crash to understand the nature of the crash better.' }; + sandboxed: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If the window for the utility process was sandboxed or not.' }; code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The exit code of the utility process to understand the nature of the crash better' }; owner: 'bpasero'; comment: 'Provides insight into reasons the utility process crashed.'; @@ -272,8 +274,14 @@ export class UtilityProcess extends Disposable { type: string; reason: string; code: number; + sandboxed: string; }; - this.telemetryService.publicLog2('utilityprocesscrash', { type: configuration.type, reason: details.reason, code: details.exitCode }); + this.telemetryService.publicLog2('utilityprocesscrash', { + type: configuration.type, + reason: details.reason, + code: details.exitCode, + sandboxed: isWindowSandboxed ? '1' : '0' // TODO@bpasero remove this once sandbox is enabled by default + }); // Event this._onCrash.fire({ pid: this.processPid!, code: details.exitCode, reason: details.reason }); diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index 8330d6f39a5..0ae587f5894 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -44,6 +44,8 @@ export interface ICodeWindow extends IDisposable { ready(): Promise; setReady(): void; + readonly isSandboxed: boolean; + addTabbedWindow(window: ICodeWindow): void; load(config: INativeWindowConfiguration, options?: { isReload?: boolean }): void; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index e2f045991fc..88cc0db453f 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -125,6 +125,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { private _lastFocusTime = -1; get lastFocusTime(): number { return this._lastFocusTime; } + private _isSandboxed = false; + get isSandboxed(): boolean { return this._isSandboxed; } + get backupPath(): string | undefined { return this._config?.backupPath; } get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this._config?.workspace; } @@ -233,6 +236,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { useSandbox = typeof this.productService.quality === 'string' && this.productService.quality !== 'stable'; } + this._isSandboxed = useSandbox; + const options: BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } = { width: this.windowState.width, height: this.windowState.height, diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index faebfc38595..03f175eda8e 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -49,6 +49,7 @@ suite('WindowsFinder', () => { lastFocusTime = options.lastFocusTime; isFullScreen = false; isReady = true; + isSandboxed = false; ready(): Promise { throw new Error('Method not implemented.'); } setReady(): void { throw new Error('Method not implemented.'); } From 395517fefa7971e278ec613be2f44f766c44ffc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 14 Feb 2023 20:14:13 +0100 Subject: [PATCH 11/85] update distro (#174372) * update distro * update distro --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d3bef22f362..a842f18f4d6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.76.0", - "distro": "ea016b36988a95ad53a905e9449f360c77fe5026", + "distro": "cd6d83a02668b3ee1577d32ede0540c6e71b8119", "author": { "name": "Microsoft Corporation" }, @@ -240,4 +240,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} +} \ No newline at end of file From e8a66223fe033d2466804bfafde0f00e2a6eb7a1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Feb 2023 21:15:07 +0100 Subject: [PATCH 12/85] HTML files default to save as .txt, but only if line 1 is not blank (fix #174048) (#174395) --- .../textfile/browser/textFileService.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 88f8842da6f..a78fc891cf9 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -591,13 +591,18 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // of untitled model if it is a valid path name and // figure out the file extension from the mode if any. + let nameCandidate: string; if (await this.pathService.hasValidBasename(joinPath(defaultFilePath, model.name), model.name)) { - const languageId = model.getLanguageId(); - if (languageId && languageId !== PLAINTEXT_LANGUAGE_ID) { - suggestedFilename = this.suggestFilename(languageId, model.name); - } else { - suggestedFilename = model.name; - } + nameCandidate = model.name; + } else { + nameCandidate = basename(resource); + } + + const languageId = model.getLanguageId(); + if (languageId && languageId !== PLAINTEXT_LANGUAGE_ID) { + suggestedFilename = this.suggestFilename(languageId, nameCandidate); + } else { + suggestedFilename = nameCandidate; } } } From 4119dea5ff126d137f75863b8e2c1c73662111ff Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 14 Feb 2023 13:34:11 -0700 Subject: [PATCH 13/85] Polish explorer tooltip rendering (#174085) * Place explorer tooltip rendering behind a setting * Implement windows hover behavior * Pass the actual mouse event rather than a synthetic click * Remove explorer setting * Remove more code that was added for the setting * Fix bad merge * Address PR comments --- .../files/browser/media/explorerviewlet.css | 8 +++ .../files/browser/views/explorerViewer.ts | 70 +++++++++++++++++-- .../workbench/services/hover/browser/hover.ts | 6 +- .../services/hover/browser/hoverService.ts | 5 ++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 89e5008d3c4..ba3fc0686f6 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -9,6 +9,14 @@ height: 100%; } +.explorer-item-hover { + /* -- Must set important as hover overrides the cursor -- */ + cursor: pointer !important; + padding-left: 6px; + height: 22px; + font-size: 13px; +} + .explorer-folders-view .monaco-list-row { padding-left: 4px; /* align top level twistie with `Explorer` title label */ } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 65a9ad14614..709f03e5948 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -48,7 +48,7 @@ import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree' import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ILabelService } from 'vs/platform/label/common/label'; -import { isNumber } from 'vs/base/common/types'; +import { isNumber, isStringArray } from 'vs/base/common/types'; import { IEditableData } from 'vs/workbench/common/views'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -62,6 +62,9 @@ import { ResourceSet } from 'vs/base/common/map'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { timeout } from 'vs/base/common/async'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -275,6 +278,64 @@ export class FilesRenderer implements ICompressibleTreeRenderer(); readonly onDidChangeActiveDescendant = this._onDidChangeActiveDescendant.event; + private readonly hoverDelegate = new class implements IHoverDelegate { + + readonly placement = 'element'; + + get delay() { + return this.configurationService.getValue('workbench.hover.delay'); + } + + constructor( + private readonly configurationService: IConfigurationService, + private readonly hoverService: IHoverService + ) { } + + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + let element: HTMLElement; + if (options.target instanceof HTMLElement) { + element = options.target; + } else { + element = options.target.targetElements[0]; + } + + const row = element.closest('.monaco-tl-row') as HTMLElement | undefined; + + const child = element.querySelector('div.monaco-icon-label-container') as Element | undefined; + const childOfChild = child?.querySelector('span.monaco-icon-name-container') as HTMLElement | undefined; + let overflowed = false; + if (childOfChild && child) { + const width = child.clientWidth; + const childWidth = childOfChild.offsetWidth; + // Check if element is overflowing its parent container + overflowed = width <= childWidth; + } + + const hasDecoration = element.classList.toString().includes('monaco-decoration-iconBadge'); + // If it's overflowing or has a decoration show the tooltip + overflowed = overflowed || hasDecoration; + + const indentGuideElement = row?.querySelector('.monaco-tl-indent') as HTMLElement | undefined; + if (!indentGuideElement) { + return; + } + + return overflowed ? this.hoverService.showHover({ + ...options, + target: indentGuideElement, + compact: true, + additionalClasses: ['explorer-item-hover'], + skipFadeInAnimation: true, + showPointer: false, + onClick: (e) => { + this.hoverService.hideHover(); + element.dispatchEvent(new MouseEvent(e.type, { ...e, bubbles: true })); + }, + hoverPosition: HoverPosition.RIGHT, + }, focus) : undefined; + } + }(this.configurationService, this.hoverService); + constructor( container: HTMLElement, private labels: ResourceLabels, @@ -285,7 +346,8 @@ export class FilesRenderer implements ICompressibleTreeRenderer(); @@ -317,8 +379,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer { try { if (templateData.currentContext) { @@ -417,6 +478,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer('hoverService'); @@ -129,6 +128,11 @@ export interface IHoverOptions { trapFocus?: boolean; /** + * A callback which will be executed when the hover is clicked + */ + onClick?(e: MouseEvent): void; + + /* * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover * in. This is particularly useful for more natural tab focusing behavior, where the hover is * created as the next tab index after the element being hovered and/or to workaround the diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index c6fbd3a8d87..ab51a888eb3 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -64,6 +64,11 @@ export class HoverService implements IHoverService { options.container ); hover.onRequestLayout(() => provider.layout()); + if (options.onClick) { + hoverDisposables.add(addDisposableListener(hover.domNode, EventType.CLICK, e => { + options.onClick!(e); + })); + } if ('targetElements' in options.target) { for (const element of options.target.targetElements) { hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover())); From 4c4565a876029cc4337a6f7fd9e09e7a34f9275a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:01:00 -0800 Subject: [PATCH 14/85] Add id to hover --- src/vs/workbench/services/hover/browser/hover.ts | 7 +++++++ src/vs/workbench/services/hover/browser/hoverService.ts | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index d04676cde8e..2d1f78c51d9 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -58,6 +58,13 @@ export interface IHoverOptions { */ target: IHoverTarget | HTMLElement; + /** + * An ID to associate with the hover to be used as an equality check. Normally when calling + * {@link IHoverService.showHover} the options object itself is used to determine if the hover + * is the same one that is already showing, when this is set, the ID will be used instead. + */ + id?: number; + /** * A set of actions for the hover's "status bar". */ diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index ab51a888eb3..0db241f848b 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -35,7 +35,7 @@ export class HoverService implements IHoverService { } showHover(options: IHoverOptions, focus?: boolean): IHoverWidget | undefined { - if (this._currentHoverOptions === options) { + if (getHoverOptionIdentity(this._currentHoverOptions) === getHoverOptionIdentity(options)) { return undefined; } this._currentHoverOptions = options; @@ -140,6 +140,13 @@ export class HoverService implements IHoverService { } } +function getHoverOptionIdentity(options: IHoverOptions | undefined): IHoverOptions | number | undefined { + if (options === undefined) { + return undefined; + } + return options?.id ?? options; +} + class HoverContextViewDelegate implements IDelegate { get anchorPosition() { From 0005b0fa69ca6e8d0f57ac41c5277bb5884c276e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:01:42 -0800 Subject: [PATCH 15/85] Fix typo --- src/vs/workbench/services/hover/browser/hoverService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index 0db241f848b..66d5b00e83b 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -35,7 +35,7 @@ export class HoverService implements IHoverService { } showHover(options: IHoverOptions, focus?: boolean): IHoverWidget | undefined { - if (getHoverOptionIdentity(this._currentHoverOptions) === getHoverOptionIdentity(options)) { + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { return undefined; } this._currentHoverOptions = options; @@ -140,7 +140,7 @@ export class HoverService implements IHoverService { } } -function getHoverOptionIdentity(options: IHoverOptions | undefined): IHoverOptions | number | undefined { +function getHoverOptionsIdentity(options: IHoverOptions | undefined): IHoverOptions | number | undefined { if (options === undefined) { return undefined; } From a69701a464e40df587ff62056e28d116fed0241d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 00:45:31 +0100 Subject: [PATCH 16/85] profile indicator (#174402) --- .../parts/activitybar/activitybarActions.ts | 31 ++++++++++++++++++- .../activitybar/media/activityaction.css | 30 +++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 25e258e3f31..1aeaf05b8a8 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -5,10 +5,11 @@ import 'vs/css!./media/activityaction'; import { localize } from 'vs/nls'; -import { EventType, addDisposableListener, EventHelper } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, EventHelper, append, $, clearNode, hide, show } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; @@ -344,6 +345,9 @@ export interface IProfileActivity extends IActivity { export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { + private profileBadge: HTMLElement | undefined; + private profileBadgeContent: HTMLElement | undefined; + constructor( action: ActivityAction, contextMenuActionsProvider: () => IAction[], @@ -360,6 +364,31 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { @IKeybindingService keybindingService: IKeybindingService, ) { super(MenuId.GlobalActivity, action, contextMenuActionsProvider, true, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); + this._register(Event.any(this.userDataProfileService.onDidUpdateCurrentProfile, this.userDataProfileService.onDidChangeCurrentProfile)(() => this.updateProfileBadge())); + } + + override render(container: HTMLElement): void { + super.render(container); + + this.profileBadge = append(container, $('.profile-badge')); + this.profileBadgeContent = append(this.profileBadge, $('.profile-badge-content')); + this.updateProfileBadge(); + } + + protected updateProfileBadge(): void { + if (!this.profileBadge || !this.profileBadgeContent) { + return; + } + + clearNode(this.profileBadgeContent); + hide(this.profileBadge); + + if (this.userDataProfileService.currentProfile.isDefault) { + return; + } + + this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase(); + show(this.profileBadge); } protected override computeTitle(): string { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 5f52051112b..1303822108e 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -128,10 +128,10 @@ outline: none; } +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .profile-badge, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge { position: absolute; - z-index: 1; top: 0; bottom: 0; margin: auto; @@ -141,6 +141,15 @@ height: 100%; } +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator, +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge { + z-index: 2; +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .profile-badge { + z-index: 1; +} + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator { pointer-events: none; } @@ -163,6 +172,24 @@ text-align: center; } +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .profile-badge .profile-badge-content { + position: absolute; + font-weight: 600; + font-size: 11px; + line-height: 10px; + top: 28px; + right: 24px; + padding: 1px; + border-radius: 3px; + background-color: var(--vscode-activityBar-background); + color: var(--vscode-activityBar-inactiveForeground); +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:active .profile-badge-content, +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .profile-badge-content, +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .profile-badge-content { + color: var(--vscode-activityBar-foreground); +} .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content { font-size: 12px; @@ -178,6 +205,7 @@ /* Right aligned */ +.monaco-workbench .activitybar.right>.content :not(.monaco-menu)>.monaco-action-bar .profile-badge, .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .badge { left: auto; right: 0; From b2cc8c48b93222cd9727c4e70be3a269b132e5d8 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 14 Feb 2023 16:25:21 -0800 Subject: [PATCH 17/85] Fix microsoft/vscode-jupyter#12839. (#174405) --- .../contrib/notebook/browser/view/renderers/backLayerWebView.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 902191135c1..17e72fb1bc3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -678,6 +678,8 @@ export class BackLayerWebView extends Themable { 'workbench.extensions.search', 'workbench.action.openSettings', '_notebook.selectKernel', + // TODO@rebornix explore open output channel with name command + 'jupyter.viewOutput' ], }); return; From cf1c783208f9a01174d7d7bd67f40211f21ae759 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 07:20:36 +0100 Subject: [PATCH 18/85] sandbox - delay utility process to restored phase --- src/vs/workbench/electron-sandbox/window.ts | 9 ++++++-- .../electron-sandbox/sharedProcessService.ts | 1 + .../utilityProcessWorkerWorkbenchService.ts | 21 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 25ad4c2fe93..4bc86f06bd8 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -69,6 +69,7 @@ import { IBannerService } from 'vs/workbench/services/banner/browser/bannerServi import { Codicon } from 'vs/base/common/codicons'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IUtilityProcessWorkerWorkbenchService } from 'vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService'; export class NativeWindow extends Disposable { @@ -122,7 +123,8 @@ export class NativeWindow extends Disposable { @ILabelService private readonly labelService: ILabelService, @IBannerService private readonly bannerService: IBannerService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IPreferencesService private readonly preferencesService: IPreferencesService + @IPreferencesService private readonly preferencesService: IPreferencesService, + @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService ) { super(); @@ -623,7 +625,10 @@ export class NativeWindow extends Disposable { // Notify some services about lifecycle phases this.lifecycleService.when(LifecyclePhase.Ready).then(() => this.nativeHostService.notifyReady()); - this.lifecycleService.when(LifecyclePhase.Restored).then(() => this.sharedProcessService.notifyRestored()); + this.lifecycleService.when(LifecyclePhase.Restored).then(() => { + this.sharedProcessService.notifyRestored(); + this.utilityProcessWorkerWorkbenchService.notifyRestored(); + }); // Check for situations that are worth warning the user about this.handleWarnings(); diff --git a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts b/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts index 6590bc91938..ce810b44b05 100644 --- a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts +++ b/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts @@ -38,6 +38,7 @@ export class SharedProcessService extends Disposable implements ISharedProcessSe // as a result. As such, make sure we await the `Restored` // phase before making a connection attempt, but also add a // timeout to be safe against possible deadlocks. + await Promise.race([this.restoredBarrier.wait(), timeout(2000)]); // Acquire a message port connected to the shared process diff --git a/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts b/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts index c507be2d617..801b5d805fc 100644 --- a/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts +++ b/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts @@ -12,6 +12,7 @@ import { IPCClient, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { generateUuid } from 'vs/base/common/uuid'; import { acquirePort } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp'; import { IOnDidTerminateUtilityrocessWorkerProcess, ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerProcess, IUtilityProcessWorkerService } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService'; +import { Barrier, timeout } from 'vs/base/common/async'; export const IUtilityProcessWorkerWorkbenchService = createDecorator('utilityProcessWorkerWorkbenchService'); @@ -61,6 +62,11 @@ export interface IUtilityProcessWorkerWorkbenchService { * allows to terminate the worker if needed. */ createWorker(process: IUtilityProcessWorkerProcess): Promise; + + /** + * Notifies the service that the workbench window has restored. + */ + notifyRestored(): void; } export class UtilityProcessWorkerWorkbenchService extends Disposable implements IUtilityProcessWorkerWorkbenchService { @@ -77,6 +83,8 @@ export class UtilityProcessWorkerWorkbenchService extends Disposable implements return this._utilityProcessWorkerService; } + private readonly restoredBarrier = new Barrier(); + constructor( readonly windowId: number, private readonly useUtilityProcess: boolean, @@ -90,6 +98,13 @@ export class UtilityProcessWorkerWorkbenchService extends Disposable implements async createWorker(process: IUtilityProcessWorkerProcess): Promise { this.logService.trace('Renderer->UtilityProcess#createWorker'); + // We want to avoid heavy utility process work to happen before + // the window has restored. As such, make sure we await the + // `Restored` phase before making a connection attempt, but also + // add a timeout to be safe against possible deadlocks. + + await Promise.race([this.restoredBarrier.wait(), timeout(2000)]); + // Get ready to acquire the message port from the utility process worker const nonce = generateUuid(); const responseChannel = 'vscode:createUtilityProcessWorkerMessageChannelResult'; @@ -119,4 +134,10 @@ export class UtilityProcessWorkerWorkbenchService extends Disposable implements return { client, onDidTerminate, dispose: () => disposables.dispose() }; } + + notifyRestored(): void { + if (!this.restoredBarrier.isOpen()) { + this.restoredBarrier.open(); + } + } } From 4285095d18e4458ceaffff95e3445a144dce6597 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 15 Feb 2023 09:39:23 +0100 Subject: [PATCH 19/85] Improve doc comment (#174438) --- src/vs/editor/browser/editorBrowser.ts | 3 ++- src/vs/monaco.d.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index f2c926f94ae..fa8ae37f759 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -863,7 +863,8 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * All decorations added through this call will get the ownerId of this editor. - * @deprecated + * @deprecated Use `createDecorationsCollection` + * @see createDecorationsCollection */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 1a565a2c399..7aecad30c49 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5612,7 +5612,8 @@ declare namespace monaco.editor { getDecorationsInRange(range: Range): IModelDecoration[] | null; /** * All decorations added through this call will get the ownerId of this editor. - * @deprecated + * @deprecated Use `createDecorationsCollection` + * @see createDecorationsCollection */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; /** From d402caf4f8c7acc320f0b8223142ddf051fc4d91 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 15 Feb 2023 10:06:35 +0100 Subject: [PATCH 20/85] Stop reconnection loop when the connection is disposed (#174439) --- .../platform/remote/common/remoteAgentConnection.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 8cbc8ec31c4..bbbddcc39d5 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -583,7 +583,8 @@ export abstract class PersistentConnection extends Disposable { return this._permanentFailure || PersistentConnection._permanentFailure; } - private _isReconnecting: boolean; + private _isReconnecting: boolean = false; + private _isDisposed: boolean = false; constructor( private readonly _connectionType: ConnectionType, @@ -593,7 +594,6 @@ export abstract class PersistentConnection extends Disposable { private readonly _reconnectionFailureIsFatal: boolean ) { super(); - this._isReconnecting = false; this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, 0, 0)); @@ -640,6 +640,11 @@ export abstract class PersistentConnection extends Disposable { } } + public override dispose(): void { + super.dispose(); + this._isDisposed = true; + } + private async _beginReconnecting(): Promise { // Only have one reconnection loop active at a time. if (this._isReconnecting) { @@ -654,7 +659,7 @@ export abstract class PersistentConnection extends Disposable { } private async _runReconnectingLoop(): Promise { - if (this._isPermanentFailure) { + if (this._isPermanentFailure || this._isDisposed) { // no more attempts! return; } @@ -735,7 +740,7 @@ export abstract class PersistentConnection extends Disposable { this._onReconnectionPermanentFailure(this.protocol.getMillisSinceLastIncomingData(), attempt + 1, false); break; } - } while (!this._isPermanentFailure); + } while (!this._isPermanentFailure && !this._isDisposed); } private _onReconnectionPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void { From a51a30b0c43fd5a8f30b31770e59fdb2e08e1369 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 15 Feb 2023 10:06:54 +0100 Subject: [PATCH 21/85] Export `RemoteAuthorityResolverError` to embedders (#174441) --- src/vs/workbench/workbench.web.main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index d3710e7515d..f7c8567da5e 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -180,6 +180,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { UserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSyncResourceProvider'; +import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; export { @@ -193,6 +194,7 @@ export { Disposable, GroupOrientation, LogLevel, + RemoteAuthorityResolverError, // Facade API env, From 770fdee6a9875c637f349367e8c6bd2a85e922e9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 10:08:57 +0100 Subject: [PATCH 22/85] Support profiles in remote (#174392) * Implement profiles on server - Ability to create profiles on server - Client manages the profiles on profile - Enable profiles in web + remote * fix tests --- .../sharedProcess/sharedProcessMain.ts | 4 +- .../abstractExtensionManagementService.ts | 2 +- .../common/extensionManagement.ts | 3 +- .../common/extensionManagementIpc.ts | 149 +++++++-- .../node/extensionManagementService.ts | 16 +- .../remote/common/remoteAgentEnvironment.ts | 5 + .../remote/common/remoteExtensionsScanner.ts | 20 ++ .../electron-main/sharedProcess.ts | 5 +- .../sharedProcess/node/sharedProcess.ts | 7 +- .../userDataProfileIpc.ts} | 62 +++- src/vs/platform/window/common/window.ts | 1 + .../windows/electron-main/windowImpl.ts | 3 +- .../electron-main/windowsMainService.ts | 1 + .../server/node/remoteAgentEnvironmentImpl.ts | 255 +--------------- src/vs/server/node/remoteExtensionsScanner.ts | 282 ++++++++++++++++++ src/vs/server/node/serverServices.ts | 9 +- src/vs/workbench/browser/web.main.ts | 16 +- .../electron-sandbox/desktop.main.ts | 18 +- .../test/browser/configurationService.test.ts | 9 +- .../common/extensionManagement.ts | 8 - .../extensionManagementChannelClient.ts | 114 +++++++ .../common/extensionManagementService.ts | 17 +- .../remoteExtensionManagementService.ts | 85 +++--- .../common/webExtensionManagementService.ts | 11 +- .../nativeExtensionManagementService.ts | 84 +----- .../remoteExtensionManagementService.ts | 57 +--- .../extensions/browser/extensionService.ts | 7 +- .../common/abstractExtensionService.ts | 2 + .../nativeExtensionService.ts | 7 +- .../test/browser/extensionService.test.ts | 4 +- .../remote/browser/remoteAgentService.ts | 4 +- .../common/abstractRemoteAgentService.ts | 39 +-- .../common/remoteAgentEnvironmentChannel.ts | 66 +--- .../remote/common/remoteAgentService.ts | 11 - .../remote/common/remoteExtensionsScanner.ts | 86 ++++++ .../electron-sandbox/remoteAgentService.ts | 4 +- .../common/remoteUserDataProfiles.ts | 180 +++++++++++ .../test/browser/workbenchTestServices.ts | 17 +- .../electron-browser/workbenchTestServices.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 2 + 40 files changed, 1042 insertions(+), 632 deletions(-) create mode 100644 src/vs/platform/remote/common/remoteExtensionsScanner.ts rename src/vs/platform/userDataProfile/{electron-sandbox/userDataProfile.ts => common/userDataProfileIpc.ts} (66%) create mode 100644 src/vs/server/node/remoteExtensionsScanner.ts create mode 100644 src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts create mode 100644 src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts create mode 100644 src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index b158b4299b4..f54a1553738 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -104,7 +104,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { UserDataProfilesCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/userDataProfilesCleaner'; @@ -249,7 +249,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService)); + const userDataProfilesService = this._register(new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home), mainProcessService.getChannel('userDataProfiles'))); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 0fde9407ded..d4b70d0f518 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -654,12 +654,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract install(vsix: URI, options?: InstallVSIXOptions): Promise; abstract installFromLocation(location: URI, profileLocation: URI): Promise; abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; + abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; abstract download(extension: IGalleryExtension, operation: InstallOperation): Promise; abstract reinstallFromGallery(extension: ILocalExtension): Promise; abstract cleanUp(): Promise; abstract onDidUpdateExtensionMetadata: Event; - abstract getMetadata(extension: ILocalExtension, profileLocation?: URI): Promise; abstract updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; protected abstract getCurrentExtensionsManifestLocation(): URI; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index c5f01490d8b..d04e46e79cb 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -458,8 +458,7 @@ export interface IExtensionManagementService { reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; getExtensionsControlManifest(): Promise; - - getMetadata(extension: ILocalExtension, profileLocation?: URI): Promise; + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; download(extension: IGalleryExtension, operation: InstallOperation): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 0e5dcbceffc..88ba111abf0 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { revive } from 'vs/base/common/marshalling'; import { cloneAndChange } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; @@ -13,8 +12,10 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI { - return URI.revive(transformer ? transformer.transformIncoming(uri) : uri); +function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI; +function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined; +function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined { + return uri ? URI.revive(transformer ? transformer.transformIncoming(uri) : uri) : undefined; } function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI { @@ -28,6 +29,10 @@ function transformIncomingExtension(extension: ILocalExtension, transformer: IUR return { ...transformed, ...{ manifest } }; } +function transformIncomingOptions(options: O | undefined, transformer: IURITransformer | null): O | undefined { + return options?.profileLocation ? transformAndReviveIncomingURIs(options, transformer ?? DefaultURITransformer) : options; +} + function transformOutgoingExtension(extension: ILocalExtension, transformer: IURITransformer | null): ILocalExtension { return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension; } @@ -51,41 +56,109 @@ export class ExtensionManagementChannel implements IServerChannel { listen(context: any, event: string): Event { const uriTransformer = this.getUriTransformer(context); switch (event) { - case 'onInstallExtension': return this.onInstallExtension; - case 'onDidInstallExtensions': return Event.map(this.onDidInstallExtensions, results => results.map(i => ({ ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local }))); - case 'onUninstallExtension': return this.onUninstallExtension; - case 'onDidUninstallExtension': return this.onDidUninstallExtension; - case 'onDidUpdateExtensionMetadata': return this.onDidUpdateExtensionMetadata; + case 'onInstallExtension': { + return Event.map(this.onInstallExtension, e => { + return { + ...e, + profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation + }; + }); + } + case 'onDidInstallExtensions': { + return Event.map(this.onDidInstallExtensions, results => + results.map(i => ({ + ...i, + local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local, + profileLocation: i.profileLocation ? transformOutgoingURI(i.profileLocation, uriTransformer) : i.profileLocation + }))); + } + case 'onUninstallExtension': { + return Event.map(this.onUninstallExtension, e => { + return { + ...e, + profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation + }; + }); + } + case 'onDidUninstallExtension': { + return Event.map(this.onDidUninstallExtension, e => { + return { + ...e, + profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation + }; + }); + } + case 'onDidUpdateExtensionMetadata': { + return Event.map(this.onDidUpdateExtensionMetadata, e => transformOutgoingExtension(e, uriTransformer)); + } } throw new Error('Invalid listen'); } - call(context: any, command: string, args?: any): Promise { + async call(context: any, command: string, args?: any): Promise { const uriTransformer: IURITransformer | null = this.getUriTransformer(context); switch (command) { - case 'zip': return this.service.zip(transformIncomingExtension(args[0], uriTransformer)).then(uri => transformOutgoingURI(uri, uriTransformer)); - case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer)); - case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer), revive(args[1])); - case 'installFromLocation': return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), URI.revive(args[1])); - case 'getManifest': return this.service.getManifest(transformIncomingURI(args[0], uriTransformer)); - case 'getTargetPlatform': return this.service.getTargetPlatform(); - case 'canInstall': return this.service.canInstall(args[0]); - case 'installFromGallery': return this.service.installFromGallery(args[0], revive(args[1])); - case 'uninstall': return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), revive(args[1])); - case 'reinstallFromGallery': return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); - case 'getInstalled': return this.service.getInstalled(args[0], URI.revive(args[1])).then(extensions => extensions.map(e => transformOutgoingExtension(e, uriTransformer))); - case 'getMetadata': return this.service.getMetadata(transformIncomingExtension(args[0], uriTransformer), URI.revive(args[1])); - case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1], URI.revive(args[2])).then(e => transformOutgoingExtension(e, uriTransformer)); - case 'getExtensionsControlManifest': return this.service.getExtensionsControlManifest(); - case 'download': return this.service.download(args[0], args[1]); - case 'cleanUp': return this.service.cleanUp(); + case 'zip': { + const extension = transformIncomingExtension(args[0], uriTransformer); + const uri = await this.service.zip(extension); + return transformOutgoingURI(uri, uriTransformer); + } + case 'unzip': { + return this.service.unzip(transformIncomingURI(args[0], uriTransformer)); + } + case 'install': { + return this.service.install(transformIncomingURI(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); + } + case 'installFromLocation': { + return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); + } + case 'getManifest': { + return this.service.getManifest(transformIncomingURI(args[0], uriTransformer)); + } + case 'getTargetPlatform': { + return this.service.getTargetPlatform(); + } + case 'canInstall': { + return this.service.canInstall(args[0]); + } + case 'installFromGallery': { + return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer)); + } + case 'uninstall': { + return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); + } + case 'reinstallFromGallery': { + return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); + } + case 'getInstalled': { + const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer)); + return extensions.map(e => transformOutgoingExtension(e, uriTransformer)); + } + case 'copyExtensions': { + return this.service.copyExtensions(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); + } + case 'updateMetadata': { + const e = await this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1], transformIncomingURI(args[2], uriTransformer)); + return transformOutgoingExtension(e, uriTransformer); + } + case 'getExtensionsControlManifest': { + return this.service.getExtensionsControlManifest(); + } + case 'download': { + return this.service.download(args[0], args[1]); + } + case 'cleanUp': { + return this.service.cleanUp(); + } } throw new Error('Invalid call'); } } +export type ExtensionEventResult = InstallExtensionEvent | InstallExtensionResult | UninstallExtensionEvent | DidUninstallExtensionEvent; + export class ExtensionManagementChannelClient extends Disposable implements IExtensionManagementService { declare readonly _serviceBrand: undefined; @@ -107,13 +180,23 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt constructor(private readonly channel: IChannel) { super(); - this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire({ identifier: e.identifier, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))); - this._register(this.channel.listen('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))))); - this._register(this.channel.listen('onUninstallExtension')(e => this._onUninstallExtension.fire({ identifier: e.identifier, profileLocation: URI.revive(e.profileLocation) }))); - this._register(this.channel.listen('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire({ ...e, profileLocation: URI.revive(e.profileLocation) }))); + this._register(this.channel.listen('onInstallExtension')(e => this.fireEvent(this._onInstallExtension, { ...e, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))); + this._register(this.channel.listen('onDidInstallExtensions')(results => this.fireEvent(this._onDidInstallExtensions, results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))))); + this._register(this.channel.listen('onUninstallExtension')(e => this.fireEvent(this._onUninstallExtension, { ...e, profileLocation: URI.revive(e.profileLocation) }))); + this._register(this.channel.listen('onDidUninstallExtension')(e => this.fireEvent(this._onDidUninstallExtension, { ...e, profileLocation: URI.revive(e.profileLocation) }))); this._register(this.channel.listen('onDidUpdateExtensionMetadata')(e => this._onDidUpdateExtensionMetadata.fire(transformIncomingExtension(e, null)))); } + protected fireEvent(event: Emitter, data: InstallExtensionEvent): void; + protected fireEvent(event: Emitter, data: InstallExtensionResult[]): void; + protected fireEvent(event: Emitter, data: UninstallExtensionEvent): void; + protected fireEvent(event: Emitter, data: DidUninstallExtensionEvent): void; + protected fireEvent(event: Emitter, data: ExtensionEventResult): void; + protected fireEvent(event: Emitter, data: ExtensionEventResult[]): void; + protected fireEvent(event: Emitter, data: E): void { + event.fire(data); + } + private isUriComponents(thing: unknown): thing is UriComponents { if (!thing) { return false; @@ -172,15 +255,15 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt .then(extensions => extensions.map(extension => transformIncomingExtension(extension, null))); } - getMetadata(local: ILocalExtension, extensionsProfileResource?: URI): Promise { - return Promise.resolve(this.channel.call('getMetadata', [local, extensionsProfileResource])); - } - updateMetadata(local: ILocalExtension, metadata: Partial, extensionsProfileResource?: URI): Promise { return Promise.resolve(this.channel.call('updateMetadata', [local, metadata, extensionsProfileResource])) .then(extension => transformIncomingExtension(extension, null)); } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + return this.channel.call('copyExtensions', [fromProfileLocation, toProfileLocation]); + } + getExtensionsControlManifest(): Promise { return Promise.resolve(this.channel.call('getExtensionsControlManifest')); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 4147a0ccdae..e38e0bf0b2a 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -169,10 +169,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return local; } - getMetadata(extension: ILocalExtension, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { - return this.extensionsScanner.scanMetadata(extension, profileLocation); - } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { @@ -209,6 +205,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.installFromGallery(galleryExtension); } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation); + } + markAsUninstalled(...extensions: IExtension[]): Promise { return this.extensionsScanner.setUninstalled(...extensions); } @@ -530,6 +530,14 @@ export class ExtensionsScanner extends Disposable { await this.withUninstalledExtensions(uninstalled => delete uninstalled[ExtensionKey.create(extension).toString()]); } + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation); + const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions + .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ + .map(async e => ([e, await this.scanMetadata(e, fromProfileLocation)]))); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, toProfileLocation); + } + private async withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { return this.uninstalledFileLimiter.queue(async () => { let raw: string | undefined; diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index f850e0d5216..d83efeb9408 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -6,6 +6,7 @@ import * as performance from 'vs/base/common/performance'; import { OperatingSystem } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; export interface IRemoteAgentEnvironment { pid: number; @@ -22,6 +23,10 @@ export interface IRemoteAgentEnvironment { arch: string; marks: performance.PerformanceMark[]; useHostProxy: boolean; + profiles: { + all: IUserDataProfile[]; + home: URI; + }; } export interface RemoteAgentConnectionContext { diff --git a/src/vs/platform/remote/common/remoteExtensionsScanner.ts b/src/vs/platform/remote/common/remoteExtensionsScanner.ts new file mode 100644 index 00000000000..792c0352bb7 --- /dev/null +++ b/src/vs/platform/remote/common/remoteExtensionsScanner.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IRemoteExtensionsScannerService = createDecorator('IRemoteExtensionsScannerService'); + +export const RemoteExtensionsScannerChannelName = 'remoteExtensionsScanner'; + +export interface IRemoteExtensionsScannerService { + readonly _serviceBrand: undefined; + + whenExtensionsReady(): Promise; + scanExtensions(): Promise; + scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise; +} diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 46fca623948..d8876d9e5ad 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -246,7 +246,10 @@ export class SharedProcess extends Disposable implements ISharedProcess { windowId: this.window.id, appRoot: this.environmentMainService.appRoot, codeCachePath: this.environmentMainService.codeCachePath, - profiles: this.userDataProfilesService.profiles, + profiles: { + home: this.userDataProfilesService.profilesHome, + all: this.userDataProfilesService.profiles, + }, userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.loggerMainService.getLogLevel(), diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index 3b568b3b57f..d01f16c6a96 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -9,7 +9,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; -import { UriDto } from 'vs/base/common/uri'; +import { UriComponents, UriDto } from 'vs/base/common/uri'; export interface ISharedProcess { @@ -29,7 +29,10 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly loggers: UriDto[]; - readonly profiles: readonly UriDto[]; + readonly profiles: { + readonly home: UriComponents; + readonly all: readonly UriDto[]; + }; readonly policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts similarity index 66% rename from src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts rename to src/vs/platform/userDataProfile/common/userDataProfileIpc.ts index 60767bf59ef..ceaa5ec8fc8 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts @@ -5,22 +5,59 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { joinPath } from 'vs/base/common/resources'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { URI, UriDto } from 'vs/base/common/uri'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { IURITransformer, transformIncomingURIs, transformOutgoingURIs } from 'vs/base/common/uriIpc'; -export class UserDataProfilesNativeService extends Disposable implements IUserDataProfilesService { +export class RemoteUserDataProfilesServiceChannel implements IServerChannel { + + constructor( + private readonly service: IUserDataProfilesService, + private readonly getUriTransformer: (requestContext: any) => IURITransformer + ) { } + + listen(context: any, event: string): Event { + const uriTransformer = this.getUriTransformer(context); + switch (event) { + case 'onDidChangeProfiles': return Event.map(this.service.onDidChangeProfiles, e => { + return { + all: e.all.map(p => transformOutgoingURIs({ ...p }, uriTransformer)), + added: e.added.map(p => transformOutgoingURIs({ ...p }, uriTransformer)), + removed: e.removed.map(p => transformOutgoingURIs({ ...p }, uriTransformer)), + updated: e.updated.map(p => transformOutgoingURIs({ ...p }, uriTransformer)) + }; + }); + } + throw new Error(`Invalid listen ${event}`); + } + + async call(context: any, command: string, args?: any): Promise { + const uriTransformer = this.getUriTransformer(context); + switch (command) { + case 'createProfile': { + const profile = await this.service.createProfile(args[0], args[1], args[2]); + return transformOutgoingURIs({ ...profile }, uriTransformer); + } + case 'updateProfile': { + let profile = reviveProfile(transformIncomingURIs(args[0], uriTransformer), this.service.profilesHome.scheme); + profile = await this.service.updateProfile(profile, args[1]); + return transformOutgoingURIs({ ...profile }, uriTransformer); + } + case 'removeProfile': { + const profile = reviveProfile(transformIncomingURIs(args[0], uriTransformer), this.service.profilesHome.scheme); + return this.service.removeProfile(profile); + } + } + throw new Error(`Invalid call ${command}`); + } +} + +export class UserDataProfilesService extends Disposable implements IUserDataProfilesService { readonly _serviceBrand: undefined; - private readonly channel: IChannel; - - readonly profilesHome: URI; - get defaultProfile(): IUserDataProfile { return this.profiles[0]; } private _profiles: IUserDataProfile[] = []; get profiles(): IUserDataProfile[] { return this._profiles; } @@ -34,12 +71,10 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa constructor( profiles: readonly UriDto[], - @IMainProcessService mainProcessService: IMainProcessService, - @IEnvironmentService environmentService: IEnvironmentService, + readonly profilesHome: URI, + private readonly channel: IChannel, ) { super(); - this.channel = mainProcessService.getChannel('userDataProfiles'); - this.profilesHome = joinPath(environmentService.userRoamingDataHome, 'profiles'); this._profiles = profiles.map(profile => reviveProfile(profile, this.profilesHome.scheme)); this._register(this.channel.listen('onDidChangeProfiles')(e => { const added = e.added.map(profile => reviveProfile(profile, this.profilesHome.scheme)); @@ -100,4 +135,3 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa } } - diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index ccb87376d87..11b1a3d7b0e 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -285,6 +285,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native backupPath?: string; profiles: { + home: UriComponents; all: readonly UriDto[]; profile: UriDto; }; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 88cc0db453f..4af29bb8635 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1087,7 +1087,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { configuration.continueOn = this.environmentMainService.continueOn; configuration.profiles = { all: this.userDataProfilesService.profiles, - profile: this.profile || this.userDataProfilesService.defaultProfile + profile: this.profile || this.userDataProfilesService.defaultProfile, + home: this.userDataProfilesService.profilesHome }; configuration.logLevel = this.loggerMainService.getLogLevel(); configuration.loggers = { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index b2282599855..a96d9456915 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1348,6 +1348,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic backupPath: options.emptyWindowBackupInfo ? join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder) : undefined, profiles: { + home: this.userDataProfilesMainService.profilesHome, all: this.userDataProfilesMainService.profiles, // Set to default profile first and resolve and update the profile // only after the workspace-backup is registered. diff --git a/src/vs/server/node/remoteAgentEnvironmentImpl.ts b/src/vs/server/node/remoteAgentEnvironmentImpl.ts index 19d01ff49c8..a991de0deae 100644 --- a/src/vs/server/node/remoteAgentEnvironmentImpl.ts +++ b/src/vs/server/node/remoteAgentEnvironmentImpl.ts @@ -8,63 +8,29 @@ import * as platform from 'vs/base/common/platform'; import * as performance from 'vs/base/common/performance'; import { URI } from 'vs/base/common/uri'; import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; -import { IRemoteAgentEnvironmentDTO, IGetEnvironmentDataArguments, IScanExtensionsArguments, IScanSingleExtensionArguments, IGetExtensionHostExitInfoArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; -import { Schemas } from 'vs/base/common/network'; +import { IRemoteAgentEnvironmentDTO, IGetEnvironmentDataArguments, IGetExtensionHostExitInfoArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { transformOutgoingURIs } from 'vs/base/common/uriIpc'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ContextKeyExpr, ContextKeyDefinedExpr, ContextKeyNotExpr, ContextKeyEqualsExpr, ContextKeyNotEqualsExpr, ContextKeyRegexExpr, IContextKeyExprMapper, ContextKeyExpression, ContextKeyInExpr, ContextKeyGreaterExpr, ContextKeyGreaterEqualsExpr, ContextKeySmallerExpr, ContextKeySmallerEqualsExpr, ContextKeyNotInExpr } from 'vs/platform/contextkey/common/contextkey'; import { listProcesses } from 'vs/base/node/ps'; import { getMachineInfo, collectWorkspaceStats } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { basename, isAbsolute, join, resolve } from 'vs/base/common/path'; +import { basename, join } from 'vs/base/common/path'; import { ProcessItem } from 'vs/base/common/processes'; -import { InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { cwd } from 'vs/base/common/process'; import { ServerConnectionToken, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken'; import { IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; -import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService'; -import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; export class RemoteAgentEnvironmentChannel implements IServerChannel { private static _namePool = 1; - private readonly whenExtensionsReady: Promise; - constructor( private readonly _connectionToken: ServerConnectionToken, private readonly _environmentService: IServerEnvironmentService, private readonly _userDataProfilesService: IUserDataProfilesService, - extensionManagementCLI: ExtensionManagementCLI, - private readonly _logService: ILogService, private readonly _extensionHostStatusService: IExtensionHostStatusService, - private readonly _extensionsScannerService: IExtensionsScannerService, ) { - if (_environmentService.args['install-builtin-extension']) { - const installOptions: InstallOptions = { isMachineScoped: !!_environmentService.args['do-not-sync'], installPreReleaseVersion: !!_environmentService.args['pre-release'] }; - performance.mark('code/server/willInstallBuiltinExtensions'); - this.whenExtensionsReady = extensionManagementCLI.installExtensions([], _environmentService.args['install-builtin-extension'], installOptions, !!_environmentService.args['force']) - .then(() => performance.mark('code/server/didInstallBuiltinExtensions'), error => { - _logService.error(error); - }); - } else { - this.whenExtensionsReady = Promise.resolve(); - } - - const extensionsToInstall = _environmentService.args['install-extension']; - if (extensionsToInstall) { - const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); - this.whenExtensionsReady - .then(() => extensionManagementCLI.installExtensions(idsOrVSIX, [], { isMachineScoped: !!_environmentService.args['do-not-sync'], installPreReleaseVersion: !!_environmentService.args['pre-release'] }, !!_environmentService.args['force'])) - .then(null, error => { - _logService.error(error); - }); - } } async call(_: any, command: string, arg?: any): Promise { @@ -74,7 +40,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { const args = arg; const uriTransformer = createURITransformer(args.remoteAuthority); - let environmentData = await this._getEnvironmentData(); + let environmentData = await this._getEnvironmentData(args.profile); environmentData = transformOutgoingURIs(environmentData, uriTransformer); return environmentData; @@ -85,59 +51,6 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { return this._extensionHostStatusService.getExitInfo(args.reconnectionToken); } - case 'whenExtensionsReady': { - await this.whenExtensionsReady; - return; - } - - case 'scanExtensions': { - await this.whenExtensionsReady; - performance.mark('code/server/willScanExtensions'); - - const args = arg; - const language = args.language; - this._logService.trace(`Scanning extensions using UI language: ${language}`); - const uriTransformer = createURITransformer(args.remoteAuthority); - - const extensionDevelopmentLocations = args.extensionDevelopmentPath && args.extensionDevelopmentPath.map(url => URI.revive(uriTransformer.transformIncoming(url))); - const extensionDevelopmentPath = extensionDevelopmentLocations ? extensionDevelopmentLocations.filter(url => url.scheme === Schemas.file).map(url => url.fsPath) : undefined; - - let extensions = await this._scanExtensions(language, extensionDevelopmentPath); - extensions = transformOutgoingURIs(extensions, uriTransformer); - - this._logService.trace('Scanned Extensions', extensions); - RemoteAgentEnvironmentChannel._massageWhenConditions(extensions); - - performance.mark('code/server/didScanExtensions'); - return extensions; - } - - case 'scanSingleExtension': { - await this.whenExtensionsReady; - const args = arg; - const language = args.language; - const isBuiltin = args.isBuiltin; - const uriTransformer = createURITransformer(args.remoteAuthority); - const extensionLocation = URI.revive(uriTransformer.transformIncoming(args.extensionLocation)); - const extensionPath = extensionLocation.scheme === Schemas.file ? extensionLocation.fsPath : null; - - if (!extensionPath) { - return null; - } - - let extension = await this._scanSingleExtension(extensionPath, isBuiltin, language); - - if (!extension) { - return null; - } - - extension = transformOutgoingURIs(extension, uriTransformer); - - RemoteAgentEnvironmentChannel._massageWhenConditions([extension]); - - return extension; - } - case 'getDiagnosticInfo': { const options = arg; const diagnosticInfo: IDiagnosticInfo = { @@ -178,120 +91,10 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { throw new Error('Not supported'); } - private static _massageWhenConditions(extensions: IExtensionDescription[]): void { - // Massage "when" conditions which mention `resourceScheme` - - interface WhenUser { when?: string } - - interface LocWhenUser { [loc: string]: WhenUser[] } - - const _mapResourceSchemeValue = (value: string, isRegex: boolean): string => { - // console.log(`_mapResourceSchemeValue: ${value}, ${isRegex}`); - return value.replace(/file/g, 'vscode-remote'); - }; - - const _mapResourceRegExpValue = (value: RegExp): RegExp => { - let flags = ''; - flags += value.global ? 'g' : ''; - flags += value.ignoreCase ? 'i' : ''; - flags += value.multiline ? 'm' : ''; - return new RegExp(_mapResourceSchemeValue(value.source, true), flags); - }; - - const _exprKeyMapper = new class implements IContextKeyExprMapper { - mapDefined(key: string): ContextKeyExpression { - return ContextKeyDefinedExpr.create(key); - } - mapNot(key: string): ContextKeyExpression { - return ContextKeyNotExpr.create(key); - } - mapEquals(key: string, value: any): ContextKeyExpression { - if (key === 'resourceScheme' && typeof value === 'string') { - return ContextKeyEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); - } else { - return ContextKeyEqualsExpr.create(key, value); - } - } - mapNotEquals(key: string, value: any): ContextKeyExpression { - if (key === 'resourceScheme' && typeof value === 'string') { - return ContextKeyNotEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); - } else { - return ContextKeyNotEqualsExpr.create(key, value); - } - } - mapGreater(key: string, value: any): ContextKeyExpression { - return ContextKeyGreaterExpr.create(key, value); - } - mapGreaterEquals(key: string, value: any): ContextKeyExpression { - return ContextKeyGreaterEqualsExpr.create(key, value); - } - mapSmaller(key: string, value: any): ContextKeyExpression { - return ContextKeySmallerExpr.create(key, value); - } - mapSmallerEquals(key: string, value: any): ContextKeyExpression { - return ContextKeySmallerEqualsExpr.create(key, value); - } - mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr { - if (key === 'resourceScheme' && regexp) { - return ContextKeyRegexExpr.create(key, _mapResourceRegExpValue(regexp)); - } else { - return ContextKeyRegexExpr.create(key, regexp); - } - } - mapIn(key: string, valueKey: string): ContextKeyInExpr { - return ContextKeyInExpr.create(key, valueKey); - } - mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr { - return ContextKeyNotInExpr.create(key, valueKey); - } - }; - - const _massageWhenUser = (element: WhenUser) => { - if (!element || !element.when || !/resourceScheme/.test(element.when)) { - return; - } - - const expr = ContextKeyExpr.deserialize(element.when); - if (!expr) { - return; - } - - const massaged = expr.map(_exprKeyMapper); - element.when = massaged.serialize(); - }; - - const _massageWhenUserArr = (elements: WhenUser[] | WhenUser) => { - if (Array.isArray(elements)) { - for (const element of elements) { - _massageWhenUser(element); - } - } else { - _massageWhenUser(elements); - } - }; - - const _massageLocWhenUser = (target: LocWhenUser) => { - for (const loc in target) { - _massageWhenUserArr(target[loc]); - } - }; - - extensions.forEach((extension) => { - if (extension.contributes) { - if (extension.contributes.menus) { - _massageLocWhenUser(extension.contributes.menus); - } - if (extension.contributes.keybindings) { - _massageWhenUserArr(extension.contributes.keybindings); - } - if (extension.contributes.views) { - _massageLocWhenUser(extension.contributes.views); - } - } - }); - } - - private async _getEnvironmentData(): Promise { + private async _getEnvironmentData(profile?: string): Promise { + if (profile && !this._userDataProfilesService.profiles.some(p => p.id === profile)) { + await this._userDataProfilesService.createProfile(profile, profile); + } return { pid: process.pid, connectionToken: (this._connectionToken.type !== ServerConnectionTokenType.None ? this._connectionToken.value : ''), @@ -306,46 +109,12 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { os: platform.OS, arch: process.arch, marks: performance.getMarks(), - useHostProxy: !!this._environmentService.args['use-host-proxy'] + useHostProxy: !!this._environmentService.args['use-host-proxy'], + profiles: { + home: this._userDataProfilesService.profilesHome, + all: [...this._userDataProfilesService.profiles].map(profile => ({ ...profile })) + } }; } - private async _scanExtensions(language: string, extensionDevelopmentPath?: string[]): Promise { - // Ensure that the language packs are available - - const [builtinExtensions, installedExtensions, developedExtensions] = await Promise.all([ - this._scanBuiltinExtensions(language), - this._scanInstalledExtensions(language), - this._scanDevelopedExtensions(language, extensionDevelopmentPath) - ]); - - return dedupExtensions(builtinExtensions, installedExtensions, developedExtensions, this._logService); - } - - private async _scanDevelopedExtensions(language: string, extensionDevelopmentPaths?: string[]): Promise { - if (extensionDevelopmentPaths) { - return (await Promise.all(extensionDevelopmentPaths.map(extensionDevelopmentPath => this._extensionsScannerService.scanOneOrMultipleExtensions(URI.file(resolve(extensionDevelopmentPath)), ExtensionType.User, { language })))) - .flat() - .map(e => toExtensionDescription(e, true)); - } - return []; - } - - private async _scanBuiltinExtensions(language: string): Promise { - const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language, useCache: true }); - return scannedExtensions.map(e => toExtensionDescription(e, false)); - } - - private async _scanInstalledExtensions(language: string): Promise { - const scannedExtensions = await this._extensionsScannerService.scanUserExtensions({ profileLocation: this._userDataProfilesService.defaultProfile.extensionsResource, language, useCache: true }); - return scannedExtensions.map(e => toExtensionDescription(e, false)); - } - - private async _scanSingleExtension(extensionPath: string, isBuiltin: boolean, language: string): Promise { - const extensionLocation = URI.file(resolve(extensionPath)); - const type = isBuiltin ? ExtensionType.System : ExtensionType.User; - const scannedExtension = await this._extensionsScannerService.scanExistingExtension(extensionLocation, type, { language }); - return scannedExtension ? toExtensionDescription(scannedExtension, false) : null; - } - } diff --git a/src/vs/server/node/remoteExtensionsScanner.ts b/src/vs/server/node/remoteExtensionsScanner.ts new file mode 100644 index 00000000000..d4b130ba630 --- /dev/null +++ b/src/vs/server/node/remoteExtensionsScanner.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isAbsolute, join, resolve } from 'vs/base/common/path'; +import * as platform from 'vs/base/common/platform'; +import { cwd } from 'vs/base/common/process'; +import { URI } from 'vs/base/common/uri'; +import * as performance from 'vs/base/common/performance'; +import { Event } from 'vs/base/common/event'; +import { IURITransformer, transformOutgoingURIs } from 'vs/base/common/uriIpc'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyExpression, ContextKeyGreaterEqualsExpr, ContextKeyGreaterExpr, ContextKeyInExpr, ContextKeyNotEqualsExpr, ContextKeyNotExpr, ContextKeyNotInExpr, ContextKeyRegexExpr, ContextKeySmallerEqualsExpr, ContextKeySmallerExpr, IContextKeyExprMapper } from 'vs/platform/contextkey/common/contextkey'; +import { InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; +import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService'; +import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; +import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { Schemas } from 'vs/base/common/network'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; + +export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + + readonly _serviceBrand: undefined; + + private readonly _whenExtensionsReady: Promise; + + constructor( + extensionManagementCLI: ExtensionManagementCLI, + environmentService: IServerEnvironmentService, + private readonly _userDataProfilesService: IUserDataProfilesService, + private readonly _extensionsScannerService: IExtensionsScannerService, + private readonly _logService: ILogService, + ) { + if (environmentService.args['install-builtin-extension']) { + const installOptions: InstallOptions = { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }; + performance.mark('code/server/willInstallBuiltinExtensions'); + this._whenExtensionsReady = extensionManagementCLI.installExtensions([], environmentService.args['install-builtin-extension'], installOptions, !!environmentService.args['force']) + .then(() => performance.mark('code/server/didInstallBuiltinExtensions'), error => { + _logService.error(error); + }); + } else { + this._whenExtensionsReady = Promise.resolve(); + } + + const extensionsToInstall = environmentService.args['install-extension']; + if (extensionsToInstall) { + const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); + this._whenExtensionsReady + .then(() => extensionManagementCLI.installExtensions(idsOrVSIX, [], { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }, !!environmentService.args['force'])) + .then(null, error => { + _logService.error(error); + }); + } + } + + whenExtensionsReady(): Promise { + return this._whenExtensionsReady; + } + + async scanExtensions(language?: string, profileLocation?: URI, extensionDevelopmentLocations?: URI[]): Promise { + await this.whenExtensionsReady(); + + performance.mark('code/server/willScanExtensions'); + + this._logService.trace(`Scanning extensions using UI language: ${language}`); + + const extensionDevelopmentPaths = extensionDevelopmentLocations ? extensionDevelopmentLocations.filter(url => url.scheme === Schemas.file).map(url => url.fsPath) : undefined; + profileLocation = profileLocation ?? this._userDataProfilesService.defaultProfile.extensionsResource; + + const extensions = await this._scanExtensions(profileLocation, language ?? platform.language, extensionDevelopmentPaths); + + this._logService.trace('Scanned Extensions', extensions); + this._massageWhenConditions(extensions); + + performance.mark('code/server/didScanExtensions'); + return extensions; + } + + async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean, language?: string): Promise { + await this.whenExtensionsReady(); + + const extensionPath = extensionLocation.scheme === Schemas.file ? extensionLocation.fsPath : null; + + if (!extensionPath) { + return null; + } + + const extension = await this._scanSingleExtension(extensionPath, isBuiltin, language ?? platform.language); + + if (!extension) { + return null; + } + + this._massageWhenConditions([extension]); + + return extension; + } + + private async _scanExtensions(profileLocation: URI, language: string, extensionDevelopmentPath?: string[]): Promise { + // Ensure that the language packs are available + + const [builtinExtensions, installedExtensions, developedExtensions] = await Promise.all([ + this._scanBuiltinExtensions(language), + this._scanInstalledExtensions(profileLocation, language), + this._scanDevelopedExtensions(language, extensionDevelopmentPath) + ]); + + return dedupExtensions(builtinExtensions, installedExtensions, developedExtensions, this._logService); + } + + private async _scanDevelopedExtensions(language: string, extensionDevelopmentPaths?: string[]): Promise { + if (extensionDevelopmentPaths) { + return (await Promise.all(extensionDevelopmentPaths.map(extensionDevelopmentPath => this._extensionsScannerService.scanOneOrMultipleExtensions(URI.file(resolve(extensionDevelopmentPath)), ExtensionType.User, { language })))) + .flat() + .map(e => toExtensionDescription(e, true)); + } + return []; + } + + private async _scanBuiltinExtensions(language: string): Promise { + const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language, useCache: true }); + return scannedExtensions.map(e => toExtensionDescription(e, false)); + } + + private async _scanInstalledExtensions(profileLocation: URI, language: string): Promise { + const scannedExtensions = await this._extensionsScannerService.scanUserExtensions({ profileLocation, language, useCache: true }); + return scannedExtensions.map(e => toExtensionDescription(e, false)); + } + + private async _scanSingleExtension(extensionPath: string, isBuiltin: boolean, language: string): Promise { + const extensionLocation = URI.file(resolve(extensionPath)); + const type = isBuiltin ? ExtensionType.System : ExtensionType.User; + const scannedExtension = await this._extensionsScannerService.scanExistingExtension(extensionLocation, type, { language }); + return scannedExtension ? toExtensionDescription(scannedExtension, false) : null; + } + + private _massageWhenConditions(extensions: IExtensionDescription[]): void { + // Massage "when" conditions which mention `resourceScheme` + + interface WhenUser { when?: string } + + interface LocWhenUser { [loc: string]: WhenUser[] } + + const _mapResourceSchemeValue = (value: string, isRegex: boolean): string => { + // console.log(`_mapResourceSchemeValue: ${value}, ${isRegex}`); + return value.replace(/file/g, 'vscode-remote'); + }; + + const _mapResourceRegExpValue = (value: RegExp): RegExp => { + let flags = ''; + flags += value.global ? 'g' : ''; + flags += value.ignoreCase ? 'i' : ''; + flags += value.multiline ? 'm' : ''; + return new RegExp(_mapResourceSchemeValue(value.source, true), flags); + }; + + const _exprKeyMapper = new class implements IContextKeyExprMapper { + mapDefined(key: string): ContextKeyExpression { + return ContextKeyDefinedExpr.create(key); + } + mapNot(key: string): ContextKeyExpression { + return ContextKeyNotExpr.create(key); + } + mapEquals(key: string, value: any): ContextKeyExpression { + if (key === 'resourceScheme' && typeof value === 'string') { + return ContextKeyEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); + } else { + return ContextKeyEqualsExpr.create(key, value); + } + } + mapNotEquals(key: string, value: any): ContextKeyExpression { + if (key === 'resourceScheme' && typeof value === 'string') { + return ContextKeyNotEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); + } else { + return ContextKeyNotEqualsExpr.create(key, value); + } + } + mapGreater(key: string, value: any): ContextKeyExpression { + return ContextKeyGreaterExpr.create(key, value); + } + mapGreaterEquals(key: string, value: any): ContextKeyExpression { + return ContextKeyGreaterEqualsExpr.create(key, value); + } + mapSmaller(key: string, value: any): ContextKeyExpression { + return ContextKeySmallerExpr.create(key, value); + } + mapSmallerEquals(key: string, value: any): ContextKeyExpression { + return ContextKeySmallerEqualsExpr.create(key, value); + } + mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr { + if (key === 'resourceScheme' && regexp) { + return ContextKeyRegexExpr.create(key, _mapResourceRegExpValue(regexp)); + } else { + return ContextKeyRegexExpr.create(key, regexp); + } + } + mapIn(key: string, valueKey: string): ContextKeyInExpr { + return ContextKeyInExpr.create(key, valueKey); + } + mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr { + return ContextKeyNotInExpr.create(key, valueKey); + } + }; + + const _massageWhenUser = (element: WhenUser) => { + if (!element || !element.when || !/resourceScheme/.test(element.when)) { + return; + } + + const expr = ContextKeyExpr.deserialize(element.when); + if (!expr) { + return; + } + + const massaged = expr.map(_exprKeyMapper); + element.when = massaged.serialize(); + }; + + const _massageWhenUserArr = (elements: WhenUser[] | WhenUser) => { + if (Array.isArray(elements)) { + for (const element of elements) { + _massageWhenUser(element); + } + } else { + _massageWhenUser(elements); + } + }; + + const _massageLocWhenUser = (target: LocWhenUser) => { + for (const loc in target) { + _massageWhenUserArr(target[loc]); + } + }; + + extensions.forEach((extension) => { + if (extension.contributes) { + if (extension.contributes.menus) { + _massageLocWhenUser(extension.contributes.menus); + } + if (extension.contributes.keybindings) { + _massageWhenUserArr(extension.contributes.keybindings); + } + if (extension.contributes.views) { + _massageLocWhenUser(extension.contributes.views); + } + } + }); + } +} + +export class RemoteExtensionsScannerChannel implements IServerChannel { + + constructor(private service: RemoteExtensionsScannerService, private getUriTransformer: (requestContext: any) => IURITransformer) { } + + listen(context: any, event: string): Event { + throw new Error('Invalid listen'); + } + + async call(context: any, command: string, args?: any): Promise { + const uriTransformer = this.getUriTransformer(context); + switch (command) { + case 'whenExtensionsReady': return this.service.whenExtensionsReady(); + case 'scanExtensions': { + const language = args[0]; + const profileLocation = args[1] ? URI.revive(uriTransformer.transformIncoming(args[1])) : undefined; + const extensionDevelopmentPath = Array.isArray(args[2]) ? args[2].map(u => URI.revive(uriTransformer.transformIncoming(u))) : undefined; + const extensions = await this.service.scanExtensions(language, profileLocation, extensionDevelopmentPath); + return extensions.map(extension => transformOutgoingURIs(extension, uriTransformer)); + } + case 'scanSingleExtension': { + const extension = await this.service.scanSingleExtension(URI.revive(uriTransformer.transformIncoming(args[0])), args[1], args[2]); + return extension ? transformOutgoingURIs(extension, uriTransformer) : null; + } + } + throw new Error('Invalid call'); + } +} diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index c51c166cf51..edb7b0067d6 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -79,6 +79,9 @@ import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement import { LogService } from 'vs/platform/log/common/logService'; import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { localize } from 'vs/nls'; +import { RemoteExtensionsScannerChannel, RemoteExtensionsScannerService } from 'vs/server/node/remoteExtensionsScanner'; +import { RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; +import { RemoteUserDataProfilesServiceChannel } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; const eventPrefix = 'monacoworkbench'; @@ -130,6 +133,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken // User Data Profiles const userDataProfilesService = new ServerUserDataProfilesService(uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); + socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); // Initialize const [, , machineId] = await Promise.all([ @@ -205,7 +209,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken instantiationService.invokeFunction(accessor => { const extensionManagementService = accessor.get(INativeServerExtensionManagementService); const extensionsScannerService = accessor.get(IExtensionsScannerService); - const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, instantiationService.createInstance(ExtensionManagementCLI), logService, extensionHostStatusService, extensionsScannerService); + const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, extensionHostStatusService); socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel); const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender); @@ -213,6 +217,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService, configurationService)); + const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI), environmentService, userDataProfilesService, extensionsScannerService, logService); + socketServer.registerChannel(RemoteExtensionsScannerChannelName, new RemoteExtensionsScannerChannel(remoteExtensionsScanner, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); + const remoteFileSystemChannel = new RemoteAgentFileSystemProviderChannel(logService, environmentService); socketServer.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, remoteFileSystemChannel); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index e5c6c33e5dd..9ff45090e6a 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -268,10 +268,6 @@ export class BrowserMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService)); - serviceCollection.set(IRemoteAgentService, remoteAgentService); - // Files const fileService = this._register(new FileService(logService)); serviceCollection.set(IWorkbenchFileService, fileService); @@ -280,8 +276,6 @@ export class BrowserMain extends Disposable { const loggerService = new FileLoggerService(logLevel, fileService); serviceCollection.set(ILoggerService, loggerService); - await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); - // URI Identity const uriIdentityService = new UriIdentityService(fileService); serviceCollection.set(IUriIdentityService, uriIdentityService); @@ -289,15 +283,17 @@ export class BrowserMain extends Disposable { // User Data Profiles const userDataProfilesService = new BrowserUserDataProfilesService(environmentService, fileService, uriIdentityService, logService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - if (environmentService.remoteAuthority) { - // Always Disabled in web with remote connection - userDataProfilesService.setEnablement(false); - } const currentProfile = userDataProfilesService.getProfileForWorkspace(workspace) ?? userDataProfilesService.defaultProfile; const userDataProfileService = new UserDataProfileService(currentProfile, userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); + // Remote Agent + const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); + serviceCollection.set(IRemoteAgentService, remoteAgentService); + + await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); + // Long running services (workspace, config, storage) const [configurationService, storageService] = await Promise.all([ this.createWorkspaceService(workspace, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index d01d405bb5e..ea8d8b0a613 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -49,7 +49,7 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; @@ -217,10 +217,6 @@ export class DesktopMain extends Disposable { const signService = ProxyChannel.toService(mainProcessService.getChannel('sign')); serviceCollection.set(ISignService, signService); - // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService, logService)); - serviceCollection.set(IRemoteAgentService, remoteAgentService); - // Files const fileService = this._register(new FileService(logService)); serviceCollection.set(IWorkbenchFileService, fileService); @@ -229,9 +225,6 @@ export class DesktopMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(mainProcessService, utilityProcessWorkerWorkbenchService, logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - // Remote Files - this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService)); - // User Data Provider fileService.registerProvider(Schemas.vscodeUserData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, logService))); @@ -240,11 +233,18 @@ export class DesktopMain extends Disposable { serviceCollection.set(IUriIdentityService, uriIdentityService); // User Data Profiles - const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService); + const userDataProfilesService = new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home), mainProcessService.getChannel('userDataProfiles')); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.profile, userDataProfilesService.profilesHome.scheme), userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); + // Remote Agent + const remoteAgentService = this._register(new RemoteAgentService(userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); + serviceCollection.set(IRemoteAgentService, remoteAgentService); + + // Remote Files + this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService)); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: Please do NOT register services here. Use `registerSingleton()` diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index e55a03e2baa..d95c45d5285 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -88,7 +88,8 @@ suite('WorkspaceContextService - Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -130,7 +131,8 @@ suite('WorkspaceContextService - Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -152,7 +154,8 @@ suite('WorkspaceContextService - Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 10ec4f9a09d..2c15566fffc 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -14,10 +14,6 @@ export type DidChangeProfileEvent = { readonly added: ILocalExtension[]; readonl export const IProfileAwareExtensionManagementService = refineServiceDecorator(IExtensionManagementService); export interface IProfileAwareExtensionManagementService extends IExtensionManagementService { - readonly onProfileAwareInstallExtension: Event; - readonly onProfileAwareDidInstallExtensions: Event; - readonly onProfileAwareUninstallExtension: Event; - readonly onProfileAwareDidUninstallExtension: Event; readonly onDidChangeProfile: Event; } @@ -58,10 +54,6 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten onDidInstallExtensions: Event; onUninstallExtension: Event; onDidUninstallExtension: Event; - onProfileAwareInstallExtension: Event; - onProfileAwareDidInstallExtensions: Event; - onProfileAwareUninstallExtension: Event; - onProfileAwareDidUninstallExtension: Event; onDidChangeProfile: Event; installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallVSIXOptions): Promise; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts new file mode 100644 index 00000000000..9332af3584f --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILocalExtension, IGalleryExtension, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionManagementChannelClient as BaseExtensionManagementChannelClient, ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { Emitter } from 'vs/base/common/event'; +import { delta } from 'vs/base/common/arrays'; +import { compare } from 'vs/base/common/strings'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; + +export abstract class ProfileAwareExtensionManagementChannelClient extends BaseExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { + + private readonly _onDidChangeProfile = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); + readonly onDidChangeProfile = this._onDidChangeProfile.event; + + constructor(channel: IChannel, + protected readonly userDataProfileService: IUserDataProfileService, + protected readonly uriIdentityService: IUriIdentityService, + ) { + super(channel); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); + } + + protected override fireEvent(event: Emitter, data: InstallExtensionEvent): Promise; + protected override fireEvent(event: Emitter, data: InstallExtensionResult[]): Promise; + protected override fireEvent(event: Emitter, data: UninstallExtensionEvent): Promise; + protected override fireEvent(event: Emitter, data: DidUninstallExtensionEvent): Promise; + protected override fireEvent(event: Emitter, data: ExtensionEventResult): Promise; + protected override fireEvent(event: Emitter, data: ExtensionEventResult[]): Promise; + protected override async fireEvent(arg0: any, arg1: any): Promise { + if (Array.isArray(arg1)) { + const event = arg0 as Emitter; + const data = arg1 as ExtensionEventResult[]; + const filtered = []; + for (const e of data) { + const result = this.filterEvent(e); + if (result instanceof Promise ? await result : result) { + filtered.push(e); + } + } + if (filtered.length) { + event.fire(filtered); + } + } else { + const event = arg0 as Emitter; + const data = arg1 as ExtensionEventResult; + const result = this.filterEvent(data); + if (result instanceof Promise ? await result : result) { + event.fire(data); + } + } + } + + override async install(vsix: URI, installOptions?: InstallVSIXOptions): Promise { + installOptions = { ...installOptions, profileLocation: await this.getProfileLocation(installOptions?.profileLocation) }; + return super.install(vsix, installOptions); + } + + override async installFromLocation(location: URI, profileLocation: URI): Promise { + return super.installFromLocation(location, await this.getProfileLocation(profileLocation)); + } + + override async installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { + installOptions = { ...installOptions, profileLocation: await this.getProfileLocation(installOptions?.profileLocation) }; + return super.installFromGallery(extension, installOptions); + } + + override async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { + options = { ...options, profileLocation: await this.getProfileLocation(options?.profileLocation) }; + return super.uninstall(extension, options); + } + + override async getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI): Promise { + return super.getInstalled(type, await this.getProfileLocation(extensionsProfileResource)); + } + + override async updateMetadata(local: ILocalExtension, metadata: Partial, extensionsProfileResource?: URI): Promise { + return super.updateMetadata(local, metadata, await this.getProfileLocation(extensionsProfileResource)); + } + + protected async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + const previousProfileLocation = await this.getProfileLocation(e.previous.extensionsResource); + const currentProfileLocation = await this.getProfileLocation(e.profile.extensionsResource); + + if (this.uriIdentityService.extUri.isEqual(previousProfileLocation, currentProfileLocation)) { + return; + } + + if (e.preserveData) { + await this.copyExtensions(previousProfileLocation, currentProfileLocation); + this._onDidChangeProfile.fire({ added: [], removed: [] }); + } else { + const oldExtensions = await this.getInstalled(ExtensionType.User, previousProfileLocation); + const newExtensions = await this.getInstalled(ExtensionType.User, currentProfileLocation); + const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); + this._onDidChangeProfile.fire({ added, removed }); + } + } + + protected getProfileLocation(profileLocation: URI): Promise; + protected getProfileLocation(profileLocation?: URI): Promise; + protected async getProfileLocation(profileLocation?: URI): Promise { + return profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource; + } + + protected abstract filterEvent(e: ExtensionEventResult): boolean | Promise; +} diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index abab8d56829..ca92c115f5c 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -42,10 +42,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench readonly onUninstallExtension: Event; readonly onDidUninstallExtension: Event; readonly onDidUpdateExtensionMetadata: Event; - readonly onProfileAwareInstallExtension: Event; - readonly onProfileAwareDidInstallExtensions: Event; - readonly onProfileAwareUninstallExtension: Event; - readonly onProfileAwareDidUninstallExtension: Event; readonly onDidChangeProfile: Event; protected readonly servers: IExtensionManagementServer[] = []; @@ -81,10 +77,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; this.onDidUpdateExtensionMetadata = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onDidUpdateExtensionMetadata); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onProfileAwareInstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareDidInstallExtensions = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onProfileAwareDidInstallExtensions); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onProfileAwareUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onProfileAwareDidUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; this.onDidChangeProfile = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; } @@ -529,17 +521,10 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return this._targetPlatformPromise; } - async getMetadata(extension: ILocalExtension): Promise { - const server = this.getServer(extension); - if (!server) { - return undefined; - } - return server.extensionManagementService.getMetadata(extension); - } - async cleanUp(): Promise { await Promise.allSettled(this.servers.map(server => server.extensionManagementService.cleanUp())); } registerParticipant() { throw new Error('Not Supported'); } + copyExtensions(): Promise { throw new Error('Not Supported'); } } diff --git a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts index 0eb60966707..303967c7d88 100644 --- a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts @@ -4,59 +4,62 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { ILocalExtension, IGalleryExtension, InstallOptions, InstallVSIXOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; +import { ProfileAwareExtensionManagementChannelClient } from 'vs/workbench/services/extensionManagement/common/extensionManagementChannelClient'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -export class RemoteExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { - - readonly onDidChangeProfile = Event.None; - get onProfileAwareInstallExtension() { return super.onInstallExtension; } - get onProfileAwareDidInstallExtensions() { return super.onDidInstallExtensions; } - get onProfileAwareUninstallExtension() { return super.onUninstallExtension; } - get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } +export class RemoteExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { constructor( channel: IChannel, - @IUserDataProfilesService private readonly userDataProfileService: IUserDataProfilesService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, + @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(channel); + super(channel, userDataProfileService, uriIdentityService); } - override getInstalled(type: ExtensionType | null = null, profileLocation?: URI): Promise { - this.validateProfileLocation({ profileLocation }); - return super.getInstalled(type); - } - - override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - options = this.validateProfileLocation(options); - return super.uninstall(extension, options); - } - - override async install(vsix: URI, options?: InstallVSIXOptions): Promise { - options = this.validateProfileLocation(options); - return super.install(vsix, options); - } - - override async installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise { - options = this.validateProfileLocation(options); - return super.installFromGallery(extension, options); - } - - private validateProfileLocation(options?: T): T | undefined { - if (options?.profileLocation) { - if (!this.uriIdentityService.extUri.isEqual(options?.profileLocation, this.userDataProfileService.defaultProfile.extensionsResource)) { - throw new Error('This opertaion is not supported in remote scenario'); - } - options = { ...options, profileLocation: undefined }; + protected async filterEvent(e: ExtensionEventResult): Promise { + if (e.applicationScoped) { + return true; } - return options; + if (!e.profileLocation && this.userDataProfileService.currentProfile.isDefault) { + return true; + } + const currentRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(this.userDataProfileService.currentProfile); + if (this.uriIdentityService.extUri.isEqual(currentRemoteProfile.extensionsResource, e.profileLocation)) { + return true; + } + return false; } + protected override getProfileLocation(profileLocation: URI): Promise; + protected override getProfileLocation(profileLocation?: URI): Promise; + protected override async getProfileLocation(profileLocation?: URI): Promise { + if (!profileLocation && this.userDataProfileService.currentProfile.isDefault) { + return undefined; + } + profileLocation = await super.getProfileLocation(profileLocation); + let profile = this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.extensionsResource, profileLocation)); + if (profile) { + profile = await this.remoteUserDataProfilesService.getRemoteProfile(profile); + } else { + profile = (await this.remoteUserDataProfilesService.getRemoteProfiles()).find(p => this.uriIdentityService.extUri.isEqual(p.extensionsResource, profileLocation)); + } + return profile?.extensionsResource; + } + + protected override async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + const previousRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(e.previous); + const currentRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(e.profile); + if (previousRemoteProfile.id !== currentRemoteProfile.id) { + return super.whenProfileChanged(e.preserveData && currentRemoteProfile.isDefault ? { ...e, preserveData: false } : e); + } + } } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 0175a3edcfa..c00c0a582f8 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -112,11 +112,6 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return this.install(location, { profileLocation }); } - async getMetadata(extension: ILocalExtension, profileLocation?: URI): Promise { - const scannedExtension = await this.webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource); - return scannedExtension?.metadata; - } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { // unset if false metadata.isMachineScoped = metadata.isMachineScoped || undefined; @@ -128,6 +123,10 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return updatedLocalExtension; } + override async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + await this.webExtensionsScannerService.copyExtensions(fromProfileLocation, toProfileLocation, e => !e.metadata?.isApplicationScoped); + } + protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise { const compatibleExtension = await super.getCompatibleVersion(extension, sameVersion, includePreRelease); if (compatibleExtension) { @@ -171,7 +170,7 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe throw new Error('This should not happen'); } if (e.preserveData) { - await this.webExtensionsScannerService.copyExtensions(previousProfileLocation, currentProfileLocation, e => !e.metadata?.isApplicationScoped); + await this.copyExtensions(previousProfileLocation, currentProfileLocation); this._onDidChangeProfile.fire({ added: [], removed: [] }); } else { const oldExtensions = await this.webExtensionsScannerService.scanUserExtensions(previousProfileLocation); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts index ac3fd967300..693c9ae4cf5 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts @@ -5,98 +5,46 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { URI } from 'vs/base/common/uri'; -import { IGalleryExtension, ILocalExtension, InstallOptions, InstallVSIXOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { Emitter, Event } from 'vs/base/common/event'; +import { ILocalExtension, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { delta } from 'vs/base/common/arrays'; -import { compare } from 'vs/base/common/strings'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { joinPath } from 'vs/base/common/resources'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { Schemas } from 'vs/base/common/network'; import { ILogService } from 'vs/platform/log/common/log'; import { IDownloadService } from 'vs/platform/download/common/download'; import { IFileService } from 'vs/platform/files/common/files'; import { generateUuid } from 'vs/base/common/uuid'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ProfileAwareExtensionManagementChannelClient } from 'vs/workbench/services/extensionManagement/common/extensionManagementChannelClient'; -export class NativeExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { - - private readonly disposables = this._register(new DisposableStore()); - - get onProfileAwareInstallExtension() { return super.onInstallExtension; } - override get onInstallExtension() { return Event.filter(this.onProfileAwareInstallExtension, e => this.filterEvent(e), this.disposables); } - - get onProfileAwareDidInstallExtensions() { return super.onDidInstallExtensions; } - override get onDidInstallExtensions() { - return Event.filter( - Event.map(this.onProfileAwareDidInstallExtensions, results => results.filter(e => this.filterEvent(e)), this.disposables), - results => results.length > 0, this.disposables); - } - - get onProfileAwareUninstallExtension() { return super.onUninstallExtension; } - override get onUninstallExtension() { return Event.filter(this.onProfileAwareUninstallExtension, e => this.filterEvent(e), this.disposables); } - - get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } - override get onDidUninstallExtension() { return Event.filter(this.onProfileAwareDidUninstallExtension, e => this.filterEvent(e), this.disposables); } - - private readonly _onDidChangeProfile = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); - readonly onDidChangeProfile = this._onDidChangeProfile.event; +export class NativeExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { constructor( channel: IChannel, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @IFileService private readonly fileService: IFileService, @IDownloadService private readonly downloadService: IDownloadService, @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, @ILogService private readonly logService: ILogService, ) { - super(channel); - this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); + super(channel, userDataProfileService, uriIdentityService); } - private filterEvent({ profileLocation, applicationScoped }: { profileLocation?: URI; applicationScoped?: boolean }): boolean { + protected filterEvent({ profileLocation, applicationScoped }: { readonly profileLocation?: URI; readonly applicationScoped?: boolean }): boolean { return applicationScoped || this.uriIdentityService.extUri.isEqual(this.userDataProfileService.currentProfile.extensionsResource, profileLocation); } override async install(vsix: URI, options?: InstallVSIXOptions): Promise { const { location, cleanup } = await this.downloadVsix(vsix); try { - options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; return await super.install(location, options); } finally { await cleanup(); } } - override installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { - installOptions = installOptions?.profileLocation ? installOptions : { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; - return super.installFromGallery(extension, installOptions); - } - - override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; - return super.uninstall(extension, options); - } - - override getInstalled(type: ExtensionType | null = null, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { - return super.getInstalled(type, profileLocation); - } - - override getMetadata(local: ILocalExtension, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { - return super.getMetadata(local, profileLocation); - } - - override updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { - return super.updateMetadata(local, metadata, profileLocation); - } - private async downloadVsix(vsix: URI): Promise<{ location: URI; cleanup: () => Promise }> { if (vsix.scheme === Schemas.file) { return { location: vsix, async cleanup() { } }; @@ -114,20 +62,4 @@ export class NativeExtensionManagementService extends ExtensionManagementChannel }; return { location, cleanup }; } - - private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { - const oldExtensions = await super.getInstalled(ExtensionType.User, e.previous.extensionsResource); - if (e.preserveData) { - const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(oldExtensions - .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ - .map(async e => ([e, await this.getMetadata(e)]))); - await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, e.profile.extensionsResource!); - this._onDidChangeProfile.fire({ added: [], removed: [] }); - } else { - const newExtensions = await this.getInstalled(ExtensionType.User); - const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); - this._onDidChangeProfile.fire({ added, removed }); - } - } - } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index faaa349995f..1eed257472b 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode, UninstallOptions, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -19,20 +18,22 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { Promises } from 'vs/base/common/async'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IFileService } from 'vs/platform/files/common/files'; +import { RemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -export class NativeRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { - - readonly onDidChangeProfile = Event.None; - get onProfileAwareInstallExtension() { return super.onInstallExtension; } - get onProfileAwareDidInstallExtensions() { return super.onDidInstallExtensions; } - get onProfileAwareUninstallExtension() { return super.onUninstallExtension; } - get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } +export class NativeRemoteExtensionManagementService extends RemoteExtensionManagementService implements IProfileAwareExtensionManagementService { constructor( channel: IChannel, private readonly localExtensionManagementServer: IExtensionManagementServer, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, + @IRemoteUserDataProfilesService remoteUserDataProfilesService: IRemoteUserDataProfilesService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -40,50 +41,16 @@ export class NativeRemoteExtensionManagementService extends ExtensionManagementC @IFileService private readonly fileService: IFileService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(channel); - } - - override getInstalled(type: ExtensionType | null = null, profileLocation?: URI): Promise { - if (profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } - return super.getInstalled(type); - } - - override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - if (options?.profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } - return super.uninstall(extension, options); + super(channel, userDataProfileService, userDataProfilesService, remoteUserDataProfilesService, uriIdentityService); } override async install(vsix: URI, options?: InstallVSIXOptions): Promise { - if (options?.profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } const local = await super.install(vsix, options); await this.installUIDependenciesAndPackedExtensions(local); return local; } - override getMetadata(local: ILocalExtension, profileLocation?: URI): Promise { - if (profileLocation) { - throw new Error('Getting metadata from a specific profile is not supported in remote scenario'); - } - return super.getMetadata(local, profileLocation); - } - - override updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { - if (profileLocation) { - throw new Error('Getting metadata from a specific profile is not supported in remote scenario'); - } - return super.updateMetadata(local, metadata, profileLocation); - } - override async installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { - if (installOptions?.profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } const local = await this.doInstallFromGallery(extension, installOptions); await this.installUIDependenciesAndPackedExtensions(local); return local; diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index ceeb59af01f..496d82be53a 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -31,6 +31,7 @@ import { IAutomatedWindow } from 'vs/platform/log/browser/log'; import { ILogService } from 'vs/platform/log/common/log'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -52,6 +53,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @@ -70,6 +72,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten extensionManifestPropertiesService, logService, remoteAgentService, + remoteExtensionsScannerService, lifecycleService, userDataProfileService ); @@ -86,7 +89,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten protected async _scanSingleExtension(extension: IExtension): Promise { if (extension.location.scheme === Schemas.vscodeRemote) { - return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); + return this._remoteExtensionsScannerService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); } const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, this._userDataProfileService.currentProfile.extensionsResource); @@ -205,7 +208,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten let [localExtensions, remoteEnv, remoteExtensions] = await Promise.all([ this._scanWebExtensions(), this._remoteAgentService.getEnvironment(), - this._remoteAgentService.scanExtensions() + this._remoteExtensionsScannerService.scanExtensions() ]); localExtensions = this._checkEnabledAndProposedAPI(localExtensions, false); remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions, false); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index f88a36f7e21..ea6247d4f43 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -37,6 +37,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostExitInfo, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -188,6 +189,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @IExtensionManifestPropertiesService protected readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, @ILogService protected readonly _logService: ILogService, @IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService, + @IRemoteExtensionsScannerService protected readonly _remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUserDataProfileService protected readonly _userDataProfileService: IUserDataProfileService, ) { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index c2276f89cde..45022c15cc3 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -52,6 +52,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { LegacyNativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; export class NativeExtensionService extends AbstractExtensionService implements IExtensionService { @@ -77,6 +78,7 @@ export class NativeExtensionService extends AbstractExtensionService implements @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @INativeHostService private readonly _nativeHostService: INativeHostService, @@ -100,6 +102,7 @@ export class NativeExtensionService extends AbstractExtensionService implements extensionManifestPropertiesService, logService, remoteAgentService, + remoteExtensionsScannerService, lifecycleService, userDataProfileService ); @@ -146,7 +149,7 @@ export class NativeExtensionService extends AbstractExtensionService implements protected _scanSingleExtension(extension: IExtension): Promise { if (extension.location.scheme === Schemas.vscodeRemote) { - return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); + return this._remoteExtensionsScannerService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); } return this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System); @@ -571,7 +574,7 @@ export class NativeExtensionService extends AbstractExtensionService implements // fetch the remote environment [remoteEnv, remoteExtensions] = await Promise.all([ this._remoteAgentService.getEnvironment(), - this._remoteAgentService.scanExtensions() + this._remoteExtensionsScannerService.scanExtensions() ]); if (!remoteEnv) { diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 88c83a94c72..7bf74c80b6d 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -29,7 +29,7 @@ import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestUserDataProfileService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestRemoteExtensionsScannerService, TestUserDataProfileService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { mock } from 'vs/base/test/common/mock'; import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -37,6 +37,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; suite('BrowserExtensionService', () => { test('pickRunningLocation', () => { @@ -183,6 +184,7 @@ suite('ExtensionService', () => { [IUserDataProfilesService, UserDataProfilesService], [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], + [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], ]); extService = instantiationService.get(IExtensionService); }); diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 2cdbe229327..3cc54d49eea 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -18,18 +18,20 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( webSocketFactory: IWebSocketFactory | null | undefined, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService signService: ISignService, @ILogService logService: ILogService ) { - super(new BrowserSocketFactory(webSocketFactory), environmentService, productService, remoteAuthorityResolverService, signService, logService); + super(new BrowserSocketFactory(webSocketFactory), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 884d1d28ef7..2b0ecd843a1 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -17,10 +17,8 @@ import { Emitter } from 'vs/base/common/event'; import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; -import { URI } from 'vs/base/common/uri'; -import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService { @@ -32,6 +30,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I constructor( socketFactory: ISocketFactory, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -60,7 +59,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I if (!this._environment) { this._environment = this._withChannel( async (channel, connection) => { - const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority); + const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority, this.userDataProfileService.currentProfile.isDefault ? undefined : this.userDataProfileService.currentProfile.id); this._remoteAuthorityResolverService._setAuthorityConnectionToken(connection.remoteAuthority, env.connectionToken); return env; }, @@ -77,37 +76,6 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I ); } - whenExtensionsReady(): Promise { - return this._withChannel( - channel => RemoteExtensionEnvironmentChannelClient.whenExtensionsReady(channel), - undefined - ); - } - - scanExtensions(skipExtensions: ExtensionIdentifier[] = []): Promise { - return this._withChannel( - async (channel, connection) => { - const scannedExtensions = await RemoteExtensionEnvironmentChannelClient.scanExtensions(channel, connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI, skipExtensions); - scannedExtensions.forEach((extension) => ImplicitActivationEvents.updateManifest(extension)); - return scannedExtensions; - }, - [] - ).then(undefined, () => []); - } - - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { - return this._withChannel( - async (channel, connection) => { - const scannedExtension = await RemoteExtensionEnvironmentChannelClient.scanSingleExtension(channel, connection.remoteAuthority, isBuiltin, extensionLocation); - if (scannedExtension !== null) { - ImplicitActivationEvents.updateManifest(scannedExtension); - } - return scannedExtension; - }, - null - ).then(undefined, () => null); - } - getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise { return this._withChannel( channel => RemoteExtensionEnvironmentChannelClient.getDiagnosticInfo(channel, options), @@ -162,6 +130,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I } return connection.withChannel('telemetry', (channel) => callback(channel, connection)); } + } class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection { diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 1973082e189..c5d56b6dffe 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -5,17 +5,18 @@ import * as platform from 'vs/base/common/platform'; import * as performance from 'vs/base/common/performance'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionHostExitInfo } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { Mutable } from 'vs/base/common/types'; +import { revive } from 'vs/base/common/marshalling'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; export interface IGetEnvironmentDataArguments { remoteAuthority: string; + profile?: string; } export interface IGetExtensionHostExitInfoArguments { @@ -23,20 +24,6 @@ export interface IGetExtensionHostExitInfoArguments { reconnectionToken: string; } -export interface IScanExtensionsArguments { - language: string; - remoteAuthority: string; - extensionDevelopmentPath: UriComponents[] | undefined; - skipExtensions: ExtensionIdentifier[]; -} - -export interface IScanSingleExtensionArguments { - language: string; - remoteAuthority: string; - isBuiltin: boolean; - extensionLocation: UriComponents; -} - export interface IRemoteAgentEnvironmentDTO { pid: number; connectionToken: string; @@ -52,13 +39,18 @@ export interface IRemoteAgentEnvironmentDTO { arch: string; marks: performance.PerformanceMark[]; useHostProxy: boolean; + profiles: { + all: UriDto; + home: UriComponents; + }; } export class RemoteExtensionEnvironmentChannelClient { - static async getEnvironmentData(channel: IChannel, remoteAuthority: string): Promise { + static async getEnvironmentData(channel: IChannel, remoteAuthority: string, profile: string | undefined): Promise { const args: IGetEnvironmentDataArguments = { - remoteAuthority + remoteAuthority, + profile }; const data = await channel.call('getEnvironmentData', args); @@ -77,7 +69,8 @@ export class RemoteExtensionEnvironmentChannelClient { os: data.os, arch: data.arch, marks: data.marks, - useHostProxy: data.useHostProxy + useHostProxy: data.useHostProxy, + profiles: revive(data.profiles) }; } @@ -89,39 +82,6 @@ export class RemoteExtensionEnvironmentChannelClient { return channel.call('getExtensionHostExitInfo', args); } - static async whenExtensionsReady(channel: IChannel): Promise { - await channel.call('whenExtensionsReady'); - } - - static async scanExtensions(channel: IChannel, remoteAuthority: string, extensionDevelopmentPath: URI[] | undefined, skipExtensions: ExtensionIdentifier[]): Promise { - const args: IScanExtensionsArguments = { - language: platform.language, - remoteAuthority, - extensionDevelopmentPath, - skipExtensions - }; - - const extensions = await channel.call('scanExtensions', args); - extensions.forEach(ext => { (ext).extensionLocation = URI.revive(ext.extensionLocation); }); - - return extensions; - } - - static async scanSingleExtension(channel: IChannel, remoteAuthority: string, isBuiltin: boolean, extensionLocation: URI): Promise { - const args: IScanSingleExtensionArguments = { - language: platform.language, - remoteAuthority, - isBuiltin, - extensionLocation - }; - - const extension = await channel.call('scanSingleExtension', args); - if (extension) { - (>extension).extensionLocation = URI.revive(extension.extensionLocation); - } - return extension; - } - static getDiagnosticInfo(channel: IChannel, options: IDiagnosticInfoOptions): Promise { return channel.call('getDiagnosticInfo', options); } diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index 0054090e83d..abaf81a080e 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -10,8 +10,6 @@ import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics import { Event } from 'vs/base/common/event'; import { PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; export const RemoteExtensionLogFileName = 'remoteagent'; @@ -42,15 +40,6 @@ export interface IRemoteAgentService { */ getRoundTripTime(): Promise; - whenExtensionsReady(): Promise; - /** - * Scan remote extensions. - */ - scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise; - /** - * Scan a single remote extension. - */ - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise; getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise; updateTelemetryLevel(telemetryLevel: TelemetryLevel): Promise; logTelemetry(eventName: string, data?: ITelemetryData): Promise; diff --git a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts new file mode 100644 index 00000000000..7c499158b40 --- /dev/null +++ b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemoteExtensionsScannerService, RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; +import * as platform from 'vs/base/common/platform'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { URI } from 'vs/base/common/uri'; +import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, + @ILogService private readonly logService: ILogService, + ) { } + + whenExtensionsReady(): Promise { + return this.withChannel( + channel => channel.call('whenExtensionsReady'), + undefined + ); + } + + async scanExtensions(): Promise { + try { + return await this.withChannel( + async (channel) => { + const profileLocation = this.userDataProfileService.currentProfile.isDefault ? undefined : (await this.remoteUserDataProfilesService.getRemoteProfile(this.userDataProfileService.currentProfile)).extensionsResource; + const scannedExtensions = await channel.call('scanExtensions', [platform.language, profileLocation, this.environmentService.extensionDevelopmentLocationURI]); + scannedExtensions.forEach((extension) => { + extension.extensionLocation = URI.revive(extension.extensionLocation); + ImplicitActivationEvents.updateManifest(extension); + }); + return scannedExtensions; + }, + [] + ); + } catch (error) { + this.logService.error(error); + return []; + } + } + + async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { + try { + return await this.withChannel( + async (channel) => { + const extension = await channel.call('scanSingleExtension', [extensionLocation, isBuiltin, platform.language]); + if (extension !== null) { + extension.extensionLocation = URI.revive(extension.extensionLocation); + ImplicitActivationEvents.updateManifest(extension); + } + return extension; + }, + null + ); + } catch (error) { + this.logService.error(error); + return null; + } + } + + private withChannel(callback: (channel: IChannel) => Promise, fallback: R): Promise { + const connection = this.remoteAgentService.getConnection(); + if (!connection) { + return Promise.resolve(fallback); + } + return connection.withChannel(RemoteExtensionsScannerChannelName, (channel) => callback(channel)); + } +} + +registerSingleton(IRemoteExtensionsScannerService, RemoteExtensionsScannerService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index 997cfc7cda5..4ab21828920 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -20,16 +20,18 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( + @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService signService: ISignService, @ILogService logService: ILogService, ) { - super(new BrowserSocketFactory(null), environmentService, productService, remoteAuthorityResolverService, signService, logService); + super(new BrowserSocketFactory(null), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } diff --git a/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts new file mode 100644 index 00000000000..d3ef612836b --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts @@ -0,0 +1,180 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { distinct } from 'vs/base/common/arrays'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; + +const associatedRemoteProfilesKey = 'associatedRemoteProfiles'; + +export const IRemoteUserDataProfilesService = createDecorator('IRemoteUserDataProfilesService'); +export interface IRemoteUserDataProfilesService { + readonly _serviceBrand: undefined; + getRemoteProfiles(): Promise; + getRemoteProfile(localProfile: IUserDataProfile): Promise; +} + +class RemoteUserDataProfilesService extends Disposable implements IRemoteUserDataProfilesService { + + readonly _serviceBrand: undefined; + + private readonly initPromise: Promise; + + private remoteUserDataProfilesService: IUserDataProfilesService | undefined; + + constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.initPromise = this.init(); + } + + private async init(): Promise { + const connection = this.remoteAgentService.getConnection(); + if (!connection) { + return; + } + + const environment = await this.remoteAgentService.getEnvironment(); + if (!environment) { + return; + } + + this.remoteUserDataProfilesService = new UserDataProfilesService(environment.profiles.all, environment.profiles.home, connection.getChannel('userDataProfiles')); + this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeLocalProfiles(e))); + + // Associate current local profile with remote profile + const remoteProfile = await this.getAssociatedRemoteProfile(this.userDataProfileService.currentProfile, this.remoteUserDataProfilesService); + if (!remoteProfile.isDefault) { + this.setAssociatedRemoteProfiles([...this.getAssociatedRemoteProfiles(), remoteProfile.id]); + } + + this.cleanUp(); + } + + private async onDidChangeLocalProfiles(e: DidChangeProfilesEvent): Promise { + for (const profile of e.removed) { + const remoteProfile = this.remoteUserDataProfilesService?.profiles.find(p => p.id === profile.id); + if (remoteProfile) { + await this.remoteUserDataProfilesService?.removeProfile(remoteProfile); + } + } + } + + async getRemoteProfiles(): Promise { + await this.initPromise; + + if (!this.remoteUserDataProfilesService) { + throw new Error('Remote profiles service not available in the current window'); + } + + return this.remoteUserDataProfilesService.profiles; + } + + async getRemoteProfile(localProfile: IUserDataProfile): Promise { + await this.initPromise; + + if (!this.remoteUserDataProfilesService) { + throw new Error('Remote profiles service not available in the current window'); + } + + return this.getAssociatedRemoteProfile(localProfile, this.remoteUserDataProfilesService); + } + + private async getAssociatedRemoteProfile(localProfile: IUserDataProfile, remoteUserDataProfilesService: IUserDataProfilesService): Promise { + // If the local profile is the default profile, return the remote default profile + if (localProfile.isDefault) { + return remoteUserDataProfilesService.defaultProfile; + } + + let profile = remoteUserDataProfilesService.profiles.find(p => p.id === localProfile.id); + if (!profile) { + profile = await remoteUserDataProfilesService.createProfile(localProfile.id, localProfile.name, { + shortName: localProfile.shortName, + transient: localProfile.isTransient, + useDefaultFlags: localProfile.useDefaultFlags, + }); + this.setAssociatedRemoteProfiles([...this.getAssociatedRemoteProfiles(), this.userDataProfileService.currentProfile.id]); + } + return profile; + } + + private getAssociatedRemoteProfiles(): string[] { + if (this.environmentService.remoteAuthority) { + const remotes = this.parseAssociatedRemoteProfiles(); + return remotes[this.environmentService.remoteAuthority] ?? []; + } + return []; + } + + private setAssociatedRemoteProfiles(profiles: string[]): void { + if (this.environmentService.remoteAuthority) { + const remotes = this.parseAssociatedRemoteProfiles(); + profiles = distinct(profiles); + if (profiles.length) { + remotes[this.environmentService.remoteAuthority] = profiles; + } else { + delete remotes[this.environmentService.remoteAuthority]; + } + if (Object.keys(remotes).length) { + this.storageService.store(associatedRemoteProfilesKey, JSON.stringify(remotes), StorageScope.APPLICATION, StorageTarget.MACHINE); + } else { + this.storageService.remove(associatedRemoteProfilesKey, StorageScope.APPLICATION); + } + } + } + + private parseAssociatedRemoteProfiles(): IStringDictionary { + if (this.environmentService.remoteAuthority) { + const value = this.storageService.get(associatedRemoteProfilesKey, StorageScope.APPLICATION); + try { + return value ? JSON.parse(value) : {}; + } catch (error) { + this.logService.error(error); + } + } + return {}; + } + + private async cleanUp(): Promise { + const associatedRemoteProfiles: string[] = []; + for (const profileId of this.getAssociatedRemoteProfiles()) { + const remoteProfile = this.remoteUserDataProfilesService?.profiles.find(p => p.id === profileId); + if (!remoteProfile) { + continue; + } + const localProfile = this.userDataProfilesService.profiles.find(p => p.id === profileId); + if (localProfile) { + if (localProfile.name !== remoteProfile.name || localProfile.shortName !== remoteProfile.shortName) { + await this.remoteUserDataProfilesService?.updateProfile(remoteProfile, { name: localProfile.name, shortName: localProfile.shortName }); + } + associatedRemoteProfiles.push(profileId); + continue; + } + if (remoteProfile) { + // Cleanup remote profiles those are not available locally + await this.remoteUserDataProfilesService?.removeProfile(remoteProfile); + } + } + this.setAssociatedRemoteProfiles(associatedRemoteProfiles); + } + +} + +registerSingleton(IRemoteUserDataProfilesService, RemoteUserDataProfilesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 078dd9d4f38..2a296009396 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -155,7 +155,7 @@ import { TestEditorWorkerService } from 'vs/editor/test/common/services/testEdit import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService'; @@ -165,6 +165,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -1920,9 +1921,6 @@ export class TestRemoteAgentService implements IRemoteAgentService { async getEnvironment(): Promise { return null; } async getRawEnvironment(): Promise { return null; } async getExtensionHostExitInfo(reconnectionToken: string): Promise { return null; } - async whenExtensionsReady(): Promise { } - scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise { throw new Error('Method not implemented.'); } - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { throw new Error('Method not implemented.'); } async getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise { return undefined; } async updateTelemetryLevel(telemetryLevel: TelemetryLevel): Promise { } async logTelemetry(eventName: string, data?: ITelemetryData): Promise { } @@ -1930,6 +1928,13 @@ export class TestRemoteAgentService implements IRemoteAgentService { async getRoundTripTime(): Promise { return undefined; } } +export class TestRemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + declare readonly _serviceBrand: undefined; + async whenExtensionsReady(): Promise { } + scanExtensions(): Promise { throw new Error('Method not implemented.'); } + scanSingleExtension(): Promise { throw new Error('Method not implemented.'); } +} + export class TestWorkbenchExtensionEnablementService implements IWorkbenchExtensionEnablementService { _serviceBrand: undefined; onEnablementChanged = Event.None; @@ -1996,9 +2001,6 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens getExtensionsControlManifest(): Promise { throw new Error('Method not implemented.'); } - getMetadata(extension: ILocalExtension): Promise | undefined> { - throw new Error('Method not implemented.'); - } async updateMetadata(local: ILocalExtension, metadata: Partial): Promise { return local; } registerParticipant(pariticipant: IExtensionManagementParticipant): void { } async getTargetPlatform(): Promise { return TargetPlatform.UNDEFINED; } @@ -2006,6 +2008,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens download(): Promise { throw new Error('Method not implemented.'); } + copyExtensions(): Promise { throw new Error('Not Supported'); } } export class TestUserDataProfileService implements IUserDataProfileService { diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index a2a0e92435f..2bb2ada89e6 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -87,7 +87,7 @@ export const TestNativeWindowConfiguration: INativeWindowConfiguration = { homeDir: homeDir, tmpDir: tmpdir(), userDataDir: getUserDataPath(args, product.nameShort), - profiles: { profile: NULL_PROFILE, all: [NULL_PROFILE] }, + profiles: { profile: NULL_PROFILE, all: [NULL_PROFILE], home: URI.file(homeDir) }, preferUtilityProcess: false, ...args }; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 20342cec9fb..88e9eabaaaa 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -86,7 +86,9 @@ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileManagement'; +import 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; import 'vs/workbench/services/remote/common/remoteExplorerService'; +import 'vs/workbench/services/remote/common/remoteExtensionsScanner'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; From 4383a7ade30ca13635eabc6e521a7ff06cda65ba Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 11:01:43 +0100 Subject: [PATCH 23/85] preserve resolver extension on switching profiles (#174445) --- .../abstractExtensionManagementService.ts | 1 + .../common/extensionManagement.ts | 1 + .../common/extensionManagementIpc.ts | 8 +++++ .../node/extensionManagementService.ts | 24 +++++++++++---- .../platform/extensions/common/extensions.ts | 2 +- .../extensionManagementChannelClient.ts | 30 ++++++++++++++----- .../common/extensionManagementService.ts | 1 + .../remoteExtensionManagementService.ts | 22 +++++++++----- .../common/webExtensionManagementService.ts | 16 ++++++++++ .../nativeExtensionManagementService.ts | 16 ++++++++-- .../test/browser/workbenchTestServices.ts | 1 + 11 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index d4b70d0f518..78eb1b06f24 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -653,6 +653,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract getManifest(vsix: URI): Promise; abstract install(vsix: URI, options?: InstallVSIXOptions): Promise; abstract installFromLocation(location: URI, profileLocation: URI): Promise; + abstract installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; abstract download(extension: IGalleryExtension, operation: InstallOperation): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index d04e46e79cb..2864289beba 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -454,6 +454,7 @@ export interface IExtensionManagementService { canInstall(extension: IGalleryExtension): Promise; installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise; installFromLocation(location: URI, profileLocation: URI): Promise; + installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 88ba111abf0..81d3ceb46f9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -113,6 +113,9 @@ export class ExtensionManagementChannel implements IServerChannel { case 'installFromLocation': { return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); } + case 'installExtensionsFromProfile': { + return this.service.installExtensionsFromProfile(args[0], transformIncomingURI(args[1], uriTransformer), transformIncomingURI(args[2], uriTransformer)); + } case 'getManifest': { return this.service.getManifest(transformIncomingURI(args[0], uriTransformer)); } @@ -234,6 +237,11 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt return Promise.resolve(this.channel.call('installFromLocation', [location, profileLocation])).then(local => transformIncomingExtension(local, null)); } + async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { + const result = await this.channel.call('installExtensionsFromProfile', [extensions, fromProfileLocation, toProfileLocation]); + return result.map(local => transformIncomingExtension(local, null)); + } + getManifest(vsix: URI): Promise { return Promise.resolve(this.channel.call('getManifest', [vsix])); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index e38e0bf0b2a..0a3a830fcf4 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -164,11 +164,22 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi if (!local) { throw new Error(`Cannot find a valid extension from the location ${location.toString()}`); } - await this.addExtensionsToProfile([local], profileLocation); + await this.addExtensionsToProfile([[local, undefined]], profileLocation); this.logService.info('Successfully installed extension', local.identifier.id, profileLocation.toString()); return local; } + async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { + this.logService.trace('ExtensionManagementService#installExtensionsFromProfile', extensions, fromProfileLocation.toString(), toProfileLocation.toString()); + const extensionsToInstall = (await this.extensionsScanner.scanExtensions(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier))); + if (extensionsToInstall.length) { + const metadata = await Promise.all(extensionsToInstall.map(e => this.extensionsScanner.scanMetadata(e, fromProfileLocation))); + await this.addExtensionsToProfile(extensionsToInstall.map((e, index) => [e, metadata[index]]), toProfileLocation); + this.logService.info('Successfully installed extensions', extensionsToInstall.map(e => e.identifier.id), toProfileLocation.toString()); + } + return extensionsToInstall; + } + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { @@ -364,15 +375,16 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } if (added.length) { - await this.addExtensionsToProfile(added, this.userDataProfilesService.defaultProfile.extensionsResource); + await this.addExtensionsToProfile(added.map(e => [e, undefined]), this.userDataProfilesService.defaultProfile.extensionsResource); this.logService.info('Added extensions to default profile from external source', added.map(e => e.identifier.id)); } } - private async addExtensionsToProfile(extensions: ILocalExtension[], profileLocation: URI): Promise { - await this.setInstalled(extensions); - await this.extensionsProfileScannerService.addExtensionsToProfile(extensions.map(local => ([local, undefined])), profileLocation); - this._onDidInstallExtensions.fire(extensions.map(local => ({ local, identifier: local.identifier, operation: InstallOperation.None, profileLocation }))); + private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise { + const localExtensions = extensions.map(e => e[0]); + await this.setInstalled(localExtensions); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, profileLocation); + this._onDidInstallExtensions.fire(localExtensions.map(local => ({ local, identifier: local.identifier, operation: InstallOperation.None, profileLocation }))); } private async setInstalled(extensions: ILocalExtension[]): Promise { diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 959cf0c61a0..7ee47275468 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -409,7 +409,7 @@ export function isAuthenticationProviderExtension(manifest: IExtensionManifest): export function isResolverExtension(manifest: IExtensionManifest, remoteAuthority: string | undefined): boolean { if (remoteAuthority) { const activationEvent = `onResolveRemoteAuthority:${getRemoteName(remoteAuthority)}`; - return manifest.activationEvents?.indexOf(activationEvent) !== -1; + return !!manifest.activationEvents?.includes(activationEvent); } return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts index 9332af3584f..08c9ccd94dc 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -5,7 +5,7 @@ import { ILocalExtension, IGalleryExtension, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionManagementChannelClient as BaseExtensionManagementChannelClient, ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -13,7 +13,7 @@ import { Emitter } from 'vs/base/common/event'; import { delta } from 'vs/base/common/arrays'; import { compare } from 'vs/base/common/strings'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { DidChangeProfileEvent, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; export abstract class ProfileAwareExtensionManagementChannelClient extends BaseExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { @@ -85,7 +85,7 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE return super.updateMetadata(local, metadata, await this.getProfileLocation(extensionsProfileResource)); } - protected async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { const previousProfileLocation = await this.getProfileLocation(e.previous.extensionsResource); const currentProfileLocation = await this.getProfileLocation(e.profile.extensionsResource); @@ -93,14 +93,30 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE return; } - if (e.preserveData) { + const eventData = await this.switchExtensionsProfile(previousProfileLocation, currentProfileLocation, e.preserveData); + this._onDidChangeProfile.fire(eventData); + } + + protected async switchExtensionsProfile(previousProfileLocation: URI, currentProfileLocation: URI, preserve: boolean | ExtensionIdentifier[]): Promise { + if (preserve === true) { await this.copyExtensions(previousProfileLocation, currentProfileLocation); - this._onDidChangeProfile.fire({ added: [], removed: [] }); + return { added: [], removed: [] }; } else { const oldExtensions = await this.getInstalled(ExtensionType.User, previousProfileLocation); const newExtensions = await this.getInstalled(ExtensionType.User, currentProfileLocation); - const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); - this._onDidChangeProfile.fire({ added, removed }); + if (Array.isArray(preserve)) { + const extensionsToInstall: IExtensionIdentifier[] = []; + for (const extension of oldExtensions) { + if (preserve.some(id => ExtensionIdentifier.equals(extension.identifier.id, id)) && + !newExtensions.some(e => ExtensionIdentifier.equals(e.identifier.id, extension.identifier.id))) { + extensionsToInstall.push(extension.identifier); + } + } + if (extensionsToInstall.length) { + await this.installExtensionsFromProfile(extensionsToInstall, previousProfileLocation, currentProfileLocation); + } + } + return delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index ca92c115f5c..183b40748e2 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -527,4 +527,5 @@ export class ExtensionManagementService extends Disposable implements IWorkbench registerParticipant() { throw new Error('Not Supported'); } copyExtensions(): Promise { throw new Error('Not Supported'); } + installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { throw new Error('Not Supported'); } } diff --git a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts index 303967c7d88..3752dc0534a 100644 --- a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts @@ -5,13 +5,14 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { URI } from 'vs/base/common/uri'; -import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { DidChangeProfileEvent, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; import { ProfileAwareExtensionManagementChannelClient } from 'vs/workbench/services/extensionManagement/common/extensionManagementChannelClient'; -import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class RemoteExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { @@ -20,7 +21,7 @@ export class RemoteExtensionManagementService extends ProfileAwareExtensionManag @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, - @IUriIdentityService uriIdentityService: IUriIdentityService, + @IUriIdentityService uriIdentityService: IUriIdentityService ) { super(channel, userDataProfileService, uriIdentityService); } @@ -55,11 +56,16 @@ export class RemoteExtensionManagementService extends ProfileAwareExtensionManag return profile?.extensionsResource; } - protected override async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { - const previousRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(e.previous); - const currentRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(e.profile); - if (previousRemoteProfile.id !== currentRemoteProfile.id) { - return super.whenProfileChanged(e.preserveData && currentRemoteProfile.isDefault ? { ...e, preserveData: false } : e); + protected override async switchExtensionsProfile(previousProfileLocation: URI, currentProfileLocation: URI, preserveData: boolean | ExtensionIdentifier[]): Promise { + const remoteProfiles = await this.remoteUserDataProfilesService.getRemoteProfiles(); + const previousProfile = remoteProfiles.find(p => this.uriIdentityService.extUri.isEqual(p.extensionsResource, previousProfileLocation)); + const currentProfile = remoteProfiles.find(p => this.uriIdentityService.extUri.isEqual(p.extensionsResource, currentProfileLocation)); + if (previousProfile?.id === currentProfile?.id) { + return { added: [], removed: [] }; } + if (preserveData === true && currentProfile?.isDefault) { + preserveData = false; + } + return super.switchExtensionsProfile(previousProfileLocation, currentProfileLocation, preserveData); } } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index c00c0a582f8..5d986edb19e 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -112,6 +112,22 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return this.install(location, { profileLocation }); } + async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { + const result: ILocalExtension[] = []; + const extensionsToInstall = (await this.webExtensionsScannerService.scanUserExtensions(fromProfileLocation)) + .filter(e => extensions.some(id => areSameExtensions(id, e.identifier))); + if (extensionsToInstall.length) { + await Promise.allSettled(extensionsToInstall.map(async e => { + let local = await this.installFromLocation(e.location, toProfileLocation); + if (e.metadata) { + local = await this.updateMetadata(local, e.metadata, fromProfileLocation); + } + result.push(local); + })); + } + return result; + } + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { // unset if false metadata.isMachineScoped = metadata.isMachineScoped || undefined; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts index 693c9ae4cf5..a6e8652a33f 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { DidChangeProfileEvent, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ILocalExtension, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -15,8 +15,9 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IDownloadService } from 'vs/platform/download/common/download'; import { IFileService } from 'vs/platform/files/common/files'; import { generateUuid } from 'vs/base/common/uuid'; -import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ProfileAwareExtensionManagementChannelClient } from 'vs/workbench/services/extensionManagement/common/extensionManagementChannelClient'; +import { ExtensionIdentifier, ExtensionType, isResolverExtension } from 'vs/platform/extensions/common/extensions'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; export class NativeExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { @@ -26,7 +27,7 @@ export class NativeExtensionManagementService extends ProfileAwareExtensionManag @IUriIdentityService uriIdentityService: IUriIdentityService, @IFileService private readonly fileService: IFileService, @IDownloadService private readonly downloadService: IDownloadService, - @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, + @INativeWorkbenchEnvironmentService private readonly nativeEnvironmentService: INativeWorkbenchEnvironmentService, @ILogService private readonly logService: ILogService, ) { super(channel, userDataProfileService, uriIdentityService); @@ -62,4 +63,13 @@ export class NativeExtensionManagementService extends ProfileAwareExtensionManag }; return { location, cleanup }; } + + protected override async switchExtensionsProfile(previousProfileLocation: URI, currentProfileLocation: URI, preserveData: boolean | ExtensionIdentifier[]): Promise { + if (!preserveData && this.nativeEnvironmentService.remoteAuthority) { + const previousInstalledExtensions = await this.getInstalled(ExtensionType.User, previousProfileLocation); + const resolverExtension = previousInstalledExtensions.find(e => isResolverExtension(e.manifest, this.nativeEnvironmentService.remoteAuthority)); + preserveData = resolverExtension ? [new ExtensionIdentifier(resolverExtension.identifier.id)] : preserveData; + } + return super.switchExtensionsProfile(previousProfileLocation, currentProfileLocation, preserveData); + } } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 2a296009396..f3bc012c9d8 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -2009,6 +2009,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens throw new Error('Method not implemented.'); } copyExtensions(): Promise { throw new Error('Not Supported'); } + installExtensionsFromProfile(): Promise { throw new Error('Not Supported'); } } export class TestUserDataProfileService implements IUserDataProfileService { From 6f5202cd2d664a3af5708018bd1eadbc09460586 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 15 Feb 2023 11:42:32 +0000 Subject: [PATCH 24/85] Show unresolved comment glyph in gutter when appropriate (#149162) (#174418) --- .../comments/browser/commentGlyphWidget.ts | 24 +++++++++++++++---- .../browser/commentThreadZoneWidget.ts | 2 ++ .../contrib/comments/browser/media/review.css | 24 ++++++++++++++----- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts index 38c9dbde21b..76d0a4a7b95 100644 --- a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts @@ -11,6 +11,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { darken, editorBackground, editorForeground, listInactiveSelectionBackground, opaque, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; +import { CommentThreadState } from 'vs/editor/common/languages'; export const overviewRulerCommentingRangeForeground = registerColor('editorGutter.commentRangeForeground', { dark: opaque(listInactiveSelectionBackground, editorBackground), light: darken(opaque(listInactiveSelectionBackground, editorBackground), .05), hcDark: Color.white, hcLight: Color.black }, nls.localize('editorGutterCommentRangeForeground', 'Editor gutter decoration color for commenting ranges. This color should be opaque.')); registerColor('editorGutter.commentGlyphForground', { dark: editorForeground, light: editorForeground, hcDark: Color.black, hcLight: Color.white }, nls.localize('editorGutterCommentGlyphForeground', 'Editor gutter decoration color for commenting glyphs.')); @@ -19,6 +20,7 @@ export class CommentGlyphWidget { public static description = 'comment-glyph-widget'; private _lineNumber!: number; private _editor: ICodeEditor; + private _threadState: CommentThreadState | undefined; private readonly _commentsDecorations: IEditorDecorationsCollection; private _commentsOptions: ModelDecorationOptions; @@ -38,18 +40,25 @@ export class CommentGlyphWidget { position: OverviewRulerLane.Center }, collapseOnReplaceEdit: true, - linesDecorationsClassName: `comment-range-glyph comment-thread` + linesDecorationsClassName: `comment-range-glyph comment-thread${this._threadState === CommentThreadState.Unresolved ? '-unresolved' : ''}` }; return ModelDecorationOptions.createDynamic(decorationOptions); } - setLineNumber(lineNumber: number): void { - this._lineNumber = lineNumber; + setThreadState(state: CommentThreadState | undefined): void { + if (this._threadState !== state) { + this._threadState = state; + this._commentsOptions = this.createDecorationOptions(); + this._updateDecorations(); + } + } + + private _updateDecorations(): void { const commentsDecorations = [{ range: { - startLineNumber: lineNumber, startColumn: 1, - endLineNumber: lineNumber, endColumn: 1 + startLineNumber: this._lineNumber, startColumn: 1, + endLineNumber: this._lineNumber, endColumn: 1 }, options: this._commentsOptions }]; @@ -57,6 +66,11 @@ export class CommentGlyphWidget { this._commentsDecorations.set(commentsDecorations); } + setLineNumber(lineNumber: number): void { + this._lineNumber = lineNumber; + this._updateDecorations(); + } + getPosition(): IContentWidgetPosition { const range = (this._commentsDecorations.length > 0 ? this._commentsDecorations.getRange(0) : null); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 1bd0dd8c8d8..26e54d5d898 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -300,6 +300,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const lineNumber = this._commentThread.range.endLineNumber; let shouldMoveWidget = false; if (this._commentGlyph) { + this._commentGlyph.setThreadState(commentThread.state); if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { shouldMoveWidget = true; this._commentGlyph.setLineNumber(lineNumber); @@ -327,6 +328,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget display(lineNumber: number) { this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber); + this._commentGlyph.setThreadState(this._commentThread.state); this._commentThreadWidget.display(this.editor.getOption(EditorOption.lineHeight)); this._disposables.add(this._commentThreadWidget.onDidResize(dimension => { diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 6338595b911..b2c0204b7f2 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -467,7 +467,8 @@ div.preview.inline .monaco-editor .comment-range-glyph { background: var(--vscode-editorGutter-commentRangeForeground); } -.monaco-editor .comment-thread:before { +.monaco-editor .comment-thread:before, +.monaco-editor .comment-thread-unresolved:before { background: var(--vscode-editorGutter-commentRangeForeground); } @@ -489,13 +490,15 @@ div.preview.inline .monaco-editor .comment-range-glyph { } .monaco-editor .margin-view-overlays .comment-range-glyph.line-hover, -.monaco-editor .margin-view-overlays .comment-range-glyph.comment-thread { +.monaco-editor .margin-view-overlays .comment-range-glyph.comment-thread, +.monaco-editor .margin-view-overlays .comment-range-glyph.comment-thread-unresolved { margin-left: 13px; } .monaco-editor .margin-view-overlays > div:hover > .comment-range-glyph.comment-diff-added:before, .monaco-editor .margin-view-overlays .comment-range-glyph.line-hover:before, -.monaco-editor .comment-range-glyph.comment-thread:before { +.monaco-editor .comment-range-glyph.comment-thread:before, +.monaco-editor .comment-range-glyph.comment-thread-unresolved:before { position: absolute; height: 100%; width: 9px; @@ -526,12 +529,13 @@ div.preview.inline .monaco-editor .comment-range-glyph { padding-left: 1px; } -.monaco-editor .comment-range-glyph.comment-thread { +.monaco-editor .comment-range-glyph.comment-thread, +.monaco-editor .comment-range-glyph.comment-thread-unresolved { z-index: 20; } -.monaco-editor .comment-range-glyph.comment-thread:before { - content: "\ea6b"; +.monaco-editor .comment-range-glyph.comment-thread:before, +.monaco-editor .comment-range-glyph.comment-thread-unresolved:before { font-family: "codicon"; font-size: 13px; width: 18px !important; @@ -543,6 +547,14 @@ div.preview.inline .monaco-editor .comment-range-glyph { padding-left: 1px; } +.monaco-editor .comment-range-glyph.comment-thread:before { + content: "\ea6b"; + +} +.monaco-editor .comment-range-glyph.comment-thread-unresolved:before { + content: "\ec0a"; +} + .monaco-editor.inline-comment .margin-view-overlays .codicon-folding-expanded, .monaco-editor.inline-comment .margin-view-overlays .codicon-folding-collapsed { margin-left: 11px; From 497396be68135c23f8cfd43b7b811e97674f52d3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 13:12:48 +0100 Subject: [PATCH 25/85] sandbox - fix process explorer name lookup --- src/vs/base/node/ps.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index df74c98c521..4cf9ef29b94 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -48,15 +48,15 @@ export function listProcesses(rootPid: number): Promise { function findName(cmd: string): string { - const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/; - const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/; - const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/; - const UTILITY_NETWORK_HINT = /--utility-sub-type=network/; - const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/; - const UTILITY_FILE_WATCHER_HOST_HINT = /--vscode-utility-kind=fileWatcher/; - const WINDOWS_CRASH_REPORTER = /--crashes-directory/; - const WINDOWS_PTY = /\\pipe\\winpty-control/; - const WINDOWS_CONSOLE_HOST = /conhost\.exe/; + const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/i; + const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/i; + const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/i; + const UTILITY_NETWORK_HINT = /--utility-sub-type=network/i; + const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/i; + const UTILITY_FILE_WATCHER_HOST_HINT = /--vscode-utility-kind=fileWatcher/i; + const WINDOWS_CRASH_REPORTER = /--crashes-directory/i; + const WINDOWS_PTY = /\\pipe\\winpty-control/i; + const WINDOWS_CONSOLE_HOST = /conhost\.exe/i; const TYPE = /--type=([a-zA-Z-]+)/; // find windows crash reporter From d8f4ed5adf327b07183c02744ce7e621267457e7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 13:13:25 +0100 Subject: [PATCH 26/85] sandbox - set `files.experimental.watcherUseUtilityProcess` to application scope --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 6b216477a06..54f7839e0d2 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -264,7 +264,8 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize('watcherUseUtilityProcess', "When enabled, the file watcher will be launched using the new UtilityProcess Electron API."), default: false, //typeof product.quality === 'string' && product.quality !== 'stable', // disabled by default in stable for now - ignoreSync: true + ignoreSync: true, + 'scope': ConfigurationScope.APPLICATION }, 'files.hotExit': hotExitConfiguration, 'files.defaultLanguage': { From 68029e0142ef68c3514fe85481d854a9350770bb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 13:16:24 +0100 Subject: [PATCH 27/85] sandbox - extract a `WindowUtilityProcess` --- .../electron-main/extensionHostStarter.ts | 14 +- .../electron-main/utilityProcess.ts | 127 +++++++++++------- .../utilityProcessWorkerMainService.ts | 4 +- 3 files changed, 90 insertions(+), 55 deletions(-) diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index 6f9be3f5ba1..5bf5f663738 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -18,7 +18,7 @@ import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; import { canUseUtilityProcess } from 'vs/base/parts/sandbox/electron-main/electronTypes'; -import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; +import { WindowUtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -27,7 +27,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter private static _lastId: number = 0; - protected readonly _extHosts: Map; + protected readonly _extHosts: Map; private _shutdown = false; constructor( @@ -36,7 +36,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter @IWindowsMainService private readonly _windowsMainService: IWindowsMainService, @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { - this._extHosts = new Map(); + this._extHosts = new Map(); // On shutdown: gracefully await extension host shutdowns this._lifecycleMainService.onWillShutdown((e) => { @@ -49,7 +49,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter // Intentionally not killing the extension host processes } - private _getExtHost(id: string): ExtensionHostProcess | UtilityProcess { + private _getExtHost(id: string): ExtensionHostProcess | WindowUtilityProcess { const extHostProcess = this._extHosts.get(id); if (!extHostProcess) { throw new Error(`Unknown extension host!`); @@ -71,7 +71,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter onDynamicError(id: string): Event<{ error: SerializedError }> { const exthost = this._getExtHost(id); - if (exthost instanceof UtilityProcess) { + if (exthost instanceof WindowUtilityProcess) { return Event.None; } @@ -91,12 +91,12 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter throw canceled(); } const id = String(++ExtensionHostStarter._lastId); - let extHost: UtilityProcess | ExtensionHostProcess; + let extHost: WindowUtilityProcess | ExtensionHostProcess; if (useUtilityProcess) { if (!canUseUtilityProcess) { throw new Error(`Cannot use UtilityProcess!`); } - extHost = new UtilityProcess(this._logService, this._windowsMainService, this._telemetryService, this._lifecycleMainService); + extHost = new WindowUtilityProcess(this._logService, this._windowsMainService, this._telemetryService, this._lifecycleMainService); } else { extHost = new ExtensionHostProcess(id, this._logService); } diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index bd5d3f7d7b2..1c80f0991a9 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -18,14 +18,6 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec export interface IUtilityProcessConfiguration { - // --- message port response related - - readonly responseWindowId: number; - readonly responseChannel: string; - readonly responseNonce: string; - - // --- utility process options - /** * A way to group utility processes of same type together. */ @@ -57,6 +49,17 @@ export interface IUtilityProcessConfiguration { * with other components. */ readonly correlationId?: string; +} + +export interface IWindowUtilityProcessConfiguration extends IUtilityProcessConfiguration { + + // --- message port response related + + readonly responseWindowId: number; + readonly responseChannel: string; + readonly responseNonce: string; + + // --- utility process options /** * If set to `true`, will terminate the utility process @@ -122,14 +125,13 @@ export class UtilityProcess extends Disposable { constructor( @ILogService private readonly logService: ILogService, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService + @ILifecycleMainService protected readonly lifecycleMainService: ILifecycleMainService ) { super(); } - private log(msg: string, severity: Severity): void { + protected log(msg: string, severity: Severity): void { let logMsg: string; if (this.configuration?.correlationId) { logMsg = `[UtilityProcess id: ${this.configuration?.correlationId}, type: ${this.configuration?.type}, pid: ${this.processPid ?? ''}]: ${msg}`; @@ -150,31 +152,31 @@ export class UtilityProcess extends Disposable { } } - private validateCanStart(configuration: IUtilityProcessConfiguration): BrowserWindow | undefined { + private validateCanStart(): boolean { if (!canUseUtilityProcess) { throw new Error('Cannot use UtilityProcess API from Electron!'); } if (this.process) { this.log('Cannot start utility process because it is already running...', Severity.Error); - return undefined; - } - const responseWindow = this.windowsMainService.getWindowById(configuration.responseWindowId)?.win; - if (!responseWindow || responseWindow.isDestroyed() || responseWindow.webContents.isDestroyed()) { - this.log('Refusing to start utility process because requesting window cannot be found or is destroyed...', Severity.Error); - return undefined; - } - - return responseWindow; - } - - start(configuration: IUtilityProcessConfiguration): boolean { - const responseWindow = this.validateCanStart(configuration); - if (!responseWindow) { return false; } + return true; + } + + start(configuration: IUtilityProcessConfiguration): boolean { + if (!this.validateCanStart()) { + return false; + } + + this.doStart(configuration); + + return true; + } + + protected doStart(configuration: IUtilityProcessConfiguration): UtilityProcessProposedApi.UtilityProcess { this.configuration = configuration; const serviceName = `${this.configuration.type}-${this.id}`; @@ -205,22 +207,12 @@ export class UtilityProcess extends Disposable { }); // Register to events - this.registerListeners(responseWindow, this.process, this.configuration, serviceName); + this.registerListeners(this.process, this.configuration, serviceName); - // Exchange message ports - this.exchangeMessagePorts(this.process, this.configuration, responseWindow); - - return true; + return this.process; } - private registerListeners(window: BrowserWindow, process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { - - // If the lifecycle of the utility process is bound to the window, - // we kill the process if the window closes or changes - if (configuration.windowLifecycleBound) { - this._register(Event.filter(this.lifecycleMainService.onWillLoadWindow, e => e.window.win === window)(() => this.kill())); - this._register(Event.fromNodeEventEmitter(window, 'closed')(() => this.kill())); - } + private registerListeners(process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { // Stdout if (process.stdout) { @@ -284,13 +276,6 @@ export class UtilityProcess extends Disposable { })); } - private exchangeMessagePorts(process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, responseWindow: BrowserWindow) { - const { port1: windowPort, port2: utilityProcessPort } = new MessageChannelMain(); - - process.postMessage('null', [utilityProcessPort]); - responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [windowPort]); - } - enableInspectPort(): boolean { if (!this.process || typeof this.processPid !== 'number') { return false; @@ -348,3 +333,53 @@ export class UtilityProcess extends Disposable { } } } + +export class WindowUtilityProcess extends UtilityProcess { + + constructor( + @ILogService logService: ILogService, + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @ITelemetryService telemetryService: ITelemetryService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService + ) { + super(logService, telemetryService, lifecycleMainService); + } + + override start(configuration: IWindowUtilityProcessConfiguration): boolean { + const responseWindow = this.windowsMainService.getWindowById(configuration.responseWindowId)?.win; + if (!responseWindow || responseWindow.isDestroyed() || responseWindow.webContents.isDestroyed()) { + this.log('Refusing to start utility process because requesting window cannot be found or is destroyed...', Severity.Error); + + return false; + } + + // Start utility process + const process = super.doStart(configuration); + + // Register to window events + this.registerWindowListeners(responseWindow, configuration); + + // Exchange message ports to window + this.exchangeMessagePorts(process, configuration, responseWindow); + + return true; + } + + private registerWindowListeners(window: BrowserWindow, configuration: IWindowUtilityProcessConfiguration): void { + + // If the lifecycle of the utility process is bound to the window, + // we kill the process if the window closes or changes + + if (configuration.windowLifecycleBound) { + this._register(Event.filter(this.lifecycleMainService.onWillLoadWindow, e => e.window.win === window)(() => this.kill())); + this._register(Event.fromNodeEventEmitter(window, 'closed')(() => this.kill())); + } + } + + private exchangeMessagePorts(process: UtilityProcessProposedApi.UtilityProcess, configuration: IWindowUtilityProcessConfiguration, responseWindow: BrowserWindow): void { + const { port1: windowPort, port2: utilityProcessPort } = new MessageChannelMain(); + + process.postMessage('null', [utilityProcessPort]); + responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [windowPort]); + } +} diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts index f43524df304..30848510d7f 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts @@ -8,7 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ILogService } from 'vs/platform/log/common/log'; import { IUtilityProcessWorkerCreateConfiguration, IOnDidTerminateUtilityrocessWorkerProcess, IUtilityProcessWorkerConfiguration, IUtilityProcessWorkerProcessExit, IUtilityProcessWorkerService } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; +import { WindowUtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { deepClone } from 'vs/base/common/objects'; import { removeDangerousEnvVariables } from 'vs/base/common/processes'; @@ -94,7 +94,7 @@ class UtilityProcessWorker extends Disposable { private readonly _onDidTerminate = this._register(new Emitter()); readonly onDidTerminate = this._onDidTerminate.event; - private readonly utilityProcess = new UtilityProcess(this.logService, this.windowsMainService, this.telemetryService, this.lifecycleMainService); + private readonly utilityProcess = new WindowUtilityProcess(this.logService, this.windowsMainService, this.telemetryService, this.lifecycleMainService); constructor( @ILogService private readonly logService: ILogService, From 1fdb84ed57e78a6ebcd398c0bb47a6ebe7a36dfd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 15 Feb 2023 14:16:38 +0100 Subject: [PATCH 28/85] theme service imported in order to access it --- .../editor/contrib/stickyScroll/browser/stickyScroll.css | 8 ++++++++ .../stickyScroll/browser/stickyScrollController.ts | 4 +++- .../contrib/stickyScroll/browser/stickyScrollWidget.ts | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index e030b549b25..4af17f5580c 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -22,6 +22,14 @@ width : 100%; } +.monaco-editor .sticky-line-root-high-contrast-dark { + border-bottom: 1px solid white; +} + +.monaco-editor .sticky-line-root-high-contrast-light { + border-bottom: 1px solid black; +} + .monaco-editor .sticky-line-root:hover { background-color: var(--vscode-editorStickyScrollHover-background); cursor : pointer; diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 26ff4c2c6ce..2a250b47b20 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -15,6 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MenuId } from 'vs/platform/actions/common/actions'; +// import { IThemeService } from 'vs/platform/theme/common/themeService'; export class StickyScrollController extends Disposable implements IEditorContribution { @@ -32,10 +33,11 @@ export class StickyScrollController extends Disposable implements IEditorContrib @IContextMenuService private readonly _contextMenuService: IContextMenuService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @IInstantiationService instaService: IInstantiationService, + // @IThemeService themeService: IThemeService ) { super(); - this._stickyScrollWidget = new StickyScrollWidget(this._editor, languageFeaturesService, instaService); + this._stickyScrollWidget = new StickyScrollWidget(this._editor, languageFeaturesService, instaService); // themeService); this._stickyLineCandidateProvider = new StickyLineCandidateProvider(this._editor, languageFeaturesService); this._widgetState = new StickyScrollWidgetState([], 0); diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 063f5f84b78..caaddd7bd38 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -20,6 +20,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import 'vs/css!./stickyScroll'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +// import { IThemeService } from 'vs/platform/theme/common/themeService'; interface CustomMouseEvent { detail: string; @@ -51,7 +52,8 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { constructor( private readonly _editor: ICodeEditor, @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, - @IInstantiationService private readonly _instaService: IInstantiationService + @IInstantiationService private readonly _instaService: IInstantiationService, + // @IThemeService private readonly _themeService: IThemeService ) { super(); this._layoutInfo = this._editor.getLayoutInfo(); @@ -249,6 +251,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { child.appendChild(lineHTMLNode); child.className = 'sticky-line-root'; + // console.log('this._themeService.getColorTheme() : ', this._themeService.getColorTheme()); child.style.lineHeight = `${lineHeight}px`; child.style.width = `${width}px`; child.style.height = `${lineHeight}px`; From c63b9a6f13230cf3bce5ee5bb99233ed9d7b31e3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 15 Feb 2023 14:56:31 +0100 Subject: [PATCH 29/85] Resolves PR conversations --- src/vs/editor/common/languages.ts | 3 +++ src/vs/editor/common/model/textModelTokens.ts | 10 +++------- .../common/model/tokenizationTextModelPart.ts | 15 ++++----------- src/vs/monaco.d.ts | 10 ---------- 4 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 40bbb50e01a..ca0bb68c6bd 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -78,6 +78,9 @@ export class EncodedTokenizationResult { } } +/** + * @internal + */ export interface IBackgroundTokenizer extends IDisposable { /** * Instructs the background tokenizer to set the tokens for the given range again. diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index f801d2ef4e6..ebee591a042 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -176,8 +176,8 @@ export class TokenizationStateStore { } } - isTokenizationComplete(_textModel: ITextModel): boolean { - return this.invalidLineStartIndex >= _textModel.getLineCount(); + isTokenizationComplete(textModel: ITextModel): boolean { + return this.invalidLineStartIndex >= textModel.getLineCount(); } } @@ -264,8 +264,6 @@ export class TextModelTokenization extends Disposable { }, }; - this.backgroundTokenizer.clear(); - if (tokenizationSupport && tokenizationSupport.createBackgroundTokenizer) { this.backgroundTokenizer.value = tokenizationSupport.createBackgroundTokenizer(this._textModel, b); } @@ -273,14 +271,12 @@ export class TextModelTokenization extends Disposable { this.backgroundTokenizer.value = this._defaultBackgroundTokenizer = new DefaultBackgroundTokenizer( this._textModel, - this._tokenizationStateStore!, + this._tokenizationStateStore, b, this._languageIdCodec ); this._defaultBackgroundTokenizer.handleChanges(); } - } else { - this.backgroundTokenizer.clear(); } } diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index 04bcc3cdf74..efe4f6cae54 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -34,7 +34,6 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz private readonly _onDidChangeTokens: Emitter = this._register(new Emitter()); public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; - private readonly _languageRegistryListener: IDisposable; private readonly _tokens: ContiguousTokensStore; private readonly _semanticTokens: SparseTokensStore; private readonly _tokenization: TextModelTokenization; @@ -54,25 +53,19 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz this._semanticTokens = new SparseTokensStore( this._languageService.languageIdCodec ); - this._tokenization = new TextModelTokenization( + this._tokenization = this._register(new TextModelTokenization( _textModel, this, this._languageService.languageIdCodec - ); + )); - this._languageRegistryListener = this._languageConfigurationService.onDidChange( + this._languageRegistryListener = this._register(this._languageConfigurationService.onDidChange( e => { if (e.affects(this._languageId)) { this._onDidChangeLanguageConfiguration.fire({}); } } - ); - } - - public override dispose(): void { - this._languageRegistryListener.dispose(); - this._tokenization.dispose(); - super.dispose(); + )); } _hasListeners(): boolean { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f8c008e67db..e8df5c79bda 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6409,16 +6409,6 @@ declare namespace monaco.languages { removeText?: number; } - export interface IBackgroundTokenizer extends IDisposable { - /** - * Instructs the background tokenizer to set the tokens for the given range again. - * - * This might be necessary if the renderer overwrote those tokens with heuristically computed ones for some viewport, - * when the change does not even propagate to that viewport. - */ - requestTokens(startLineNumber: number, endLineNumberExclusive: number): void; - } - /** * The state of the tokenizer between two lines. * It is useful to store flags such as in multiline comment, etc. From 6c123a76b44824a638c0028e329b496f71536057 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 15 Feb 2023 14:59:52 +0100 Subject: [PATCH 30/85] Fixes CI --- src/vs/editor/common/model/tokenizationTextModelPart.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index efe4f6cae54..603c0214087 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { CharCode } from 'vs/base/common/charCode'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; @@ -59,7 +58,7 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz this._languageService.languageIdCodec )); - this._languageRegistryListener = this._register(this._languageConfigurationService.onDidChange( + this._register(this._languageConfigurationService.onDidChange( e => { if (e.affects(this._languageId)) { this._onDidChangeLanguageConfiguration.fire({}); From 079a3026ac442ec68ed28109e5ebffe4d18f7bc7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 15 Feb 2023 15:42:29 +0100 Subject: [PATCH 31/85] Adding code to solve the issue --- .../stickyScroll/browser/stickyScroll.css | 8 ++--- .../browser/stickyScrollController.ts | 7 ++-- .../browser/stickyScrollWidget.ts | 34 +++++++++++++++++-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index 4af17f5580c..31b8293ec39 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -22,12 +22,8 @@ width : 100%; } -.monaco-editor .sticky-line-root-high-contrast-dark { - border-bottom: 1px solid white; -} - -.monaco-editor .sticky-line-root-high-contrast-light { - border-bottom: 1px solid black; +.monaco-editor .sticky-widget.high-contrast{ + border-bottom: 1px solid var(--vscode-contrastBorder); } .monaco-editor .sticky-line-root:hover { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 2a250b47b20..83c79f45b5f 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MenuId } from 'vs/platform/actions/common/actions'; -// import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; export class StickyScrollController extends Disposable implements IEditorContribution { @@ -33,11 +33,10 @@ export class StickyScrollController extends Disposable implements IEditorContrib @IContextMenuService private readonly _contextMenuService: IContextMenuService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @IInstantiationService instaService: IInstantiationService, - // @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService ) { super(); - - this._stickyScrollWidget = new StickyScrollWidget(this._editor, languageFeaturesService, instaService); // themeService); + this._stickyScrollWidget = new StickyScrollWidget(this._editor, languageFeaturesService, instaService, themeService); this._stickyLineCandidateProvider = new StickyLineCandidateProvider(this._editor, languageFeaturesService); this._widgetState = new StickyScrollWidgetState([], 0); diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index caaddd7bd38..9477ba667b4 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -20,7 +20,14 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import 'vs/css!./stickyScroll'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -// import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +export enum ColorScheme { + DARK = 'dark', + LIGHT = 'light', + HIGH_CONTRAST_LIGHT = 'hcLight', + HIGH_CONTRAST_DARK = 'hcDark' +} interface CustomMouseEvent { detail: string; @@ -48,12 +55,13 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { private _hoverOnColumn: number = -1; private _stickyRangeProjectedOnEditor: IRange | undefined; private _candidateDefinitionsLength: number = -1; + private _colorSchemeType: ColorScheme | undefined; constructor( private readonly _editor: ICodeEditor, @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, @IInstantiationService private readonly _instaService: IInstantiationService, - // @IThemeService private readonly _themeService: IThemeService + @IThemeService private readonly _themeService: IThemeService ) { super(); this._layoutInfo = this._editor.getLayoutInfo(); @@ -61,7 +69,18 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._rootDomNode.className = 'sticky-widget'; this._rootDomNode.classList.toggle('peek', _editor instanceof EmbeddedCodeEditorWidget); this._rootDomNode.style.width = `${this._layoutInfo.width - this._layoutInfo.minimap.minimapCanvasOuterWidth - this._layoutInfo.verticalScrollbarWidth}px`; + this._colorSchemeType = this._themeService.getColorTheme().type; + // Listener on the color theme is used in order to keep track of whether a border on the sticky widget is needed + this._themeService.onDidColorThemeChange((colorTheme) => { + this._colorSchemeType = colorTheme.type; + // classList.toggle() is not used because if we move from HIGH_CONTRAST_DARK to HIGH_CONTRAST_LIGHT we want to keep the class name 'high-contrast' + if (this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) { + this._rootDomNode.classList.add('high-contrast'); + } else { + this._rootDomNode.classList.remove('high-contrast'); + } + }); this._register(this._updateLinkGesture()); } @@ -251,7 +270,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { child.appendChild(lineHTMLNode); child.className = 'sticky-line-root'; - // console.log('this._themeService.getColorTheme() : ', this._themeService.getColorTheme()); child.style.lineHeight = `${lineHeight}px`; child.style.width = `${width}px`; child.style.height = `${lineHeight}px`; @@ -287,6 +305,16 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { const editorLineHeight = this._editor.getOption(EditorOption.lineHeight); const widgetHeight: number = this._lineNumbers.length * editorLineHeight + this._lastLineRelativePosition; this._rootDomNode.style.height = widgetHeight.toString() + 'px'; + if (this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) { + // When the widget height is zero remove the bottom border, else there will be a double border below the breadcrumbs bar + if (widgetHeight === 0) { + this._rootDomNode.classList.remove('high-contrast'); + } + // Otherwise if the widget height is bigger than 0 and previously the class name 'high-contrast' was removed, set it again + else if (!this._rootDomNode.classList.contains('high-contrast')) { + this._rootDomNode.classList.add('high-contrast'); + } + } const minimapSide = this._editor.getOption(EditorOption.minimap).side; if (minimapSide === 'left') { this._rootDomNode.style.marginLeft = this._editor.getLayoutInfo().minimap.minimapCanvasOuterWidth + 'px'; From 68a7a39382d3aa2b118e6311f3fd5000918c8b25 Mon Sep 17 00:00:00 2001 From: Bassim EL BAKKALI EL GAZUANI <124774256+DerDemystifier@users.noreply.github.com> Date: Wed, 15 Feb 2023 15:51:43 +0100 Subject: [PATCH 32/85] Set Opacity to MatchHighlight in QuietLight (#174296) As stated in the docs for "editor.findMatchHighlightBackground": "The color must not be opaque so as not to hide underlying decorations." I've simply added a 0.6 opacity to the color so that it doesn't obscure the selection background. --- extensions/theme-quietlight/themes/quietlight-color-theme.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index e53088149f2..9d55f2e362b 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -500,7 +500,7 @@ "inputOption.activeBorder": "#adafb7", "dropdown.background": "#F5F5F5", "editor.findMatchBackground": "#BF9CAC", - "editor.findMatchHighlightBackground": "#edc9d8", + "editor.findMatchHighlightBackground": "#edc9d899", "peekViewEditor.matchHighlightBackground": "#C2DFE3", "peekViewTitle.background": "#F2F8FC", "peekViewEditor.background": "#F2F8FC", From a678c5396847c5e80d204342d90a664d560d8cea Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 15 Feb 2023 16:00:36 +0100 Subject: [PATCH 33/85] adding the class tag depending on the height of the widget --- .../stickyScroll/browser/stickyScrollWidget.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 9477ba667b4..b4cb9ba217f 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -56,6 +56,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { private _stickyRangeProjectedOnEditor: IRange | undefined; private _candidateDefinitionsLength: number = -1; private _colorSchemeType: ColorScheme | undefined; + private _widgetHeight: number = 0; constructor( private readonly _editor: ICodeEditor, @@ -75,7 +76,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._themeService.onDidColorThemeChange((colorTheme) => { this._colorSchemeType = colorTheme.type; // classList.toggle() is not used because if we move from HIGH_CONTRAST_DARK to HIGH_CONTRAST_LIGHT we want to keep the class name 'high-contrast' - if (this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) { + if ((this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK + || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) + && this._widgetHeight > 0) { this._rootDomNode.classList.add('high-contrast'); } else { this._rootDomNode.classList.remove('high-contrast'); @@ -303,11 +306,11 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._rootDomNode.appendChild(this._renderChildNode(index, line)); } const editorLineHeight = this._editor.getOption(EditorOption.lineHeight); - const widgetHeight: number = this._lineNumbers.length * editorLineHeight + this._lastLineRelativePosition; - this._rootDomNode.style.height = widgetHeight.toString() + 'px'; + this._widgetHeight = this._lineNumbers.length * editorLineHeight + this._lastLineRelativePosition; + this._rootDomNode.style.height = this._widgetHeight.toString() + 'px'; if (this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) { // When the widget height is zero remove the bottom border, else there will be a double border below the breadcrumbs bar - if (widgetHeight === 0) { + if (this._widgetHeight === 0) { this._rootDomNode.classList.remove('high-contrast'); } // Otherwise if the widget height is bigger than 0 and previously the class name 'high-contrast' was removed, set it again From 3a877e7d4932b0bb9c83e8568970bfe04abf1f4b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Feb 2023 07:55:43 -0800 Subject: [PATCH 34/85] Allow string in IHoverOptions.id --- src/vs/workbench/services/hover/browser/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index 2d1f78c51d9..cd428efb0a4 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -63,7 +63,7 @@ export interface IHoverOptions { * {@link IHoverService.showHover} the options object itself is used to determine if the hover * is the same one that is already showing, when this is set, the ID will be used instead. */ - id?: number; + id?: number | string; /** * A set of actions for the hover's "status bar". From 9197f4d3c9925ae82f3d5c03b32db99261a8f2e6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 16:59:16 +0100 Subject: [PATCH 35/85] fix #174463 (#174464) --- .../common/extensionsProfileScannerService.ts | 4 ++-- .../extensionsProfileScannerService.test.ts | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index 0581b2ec357..e73fe13aa14 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -248,7 +248,7 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable this.reportAndThrowInvalidConentError(file); } let location: URI; - if (isString(e.relativeLocation)) { + if (isString(e.relativeLocation) && e.relativeLocation) { // Extension in new format. No migration needed. location = this.resolveExtensionLocation(e.relativeLocation); } else if (isString(e.location)) { @@ -396,7 +396,7 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable function isStoredProfileExtension(candidate: any): candidate is IStoredProfileExtension { return isObject(candidate) && isIExtensionIdentifier(candidate.identifier) - && (isUriComponents(candidate.location) || isString(candidate.location)) + && (isUriComponents(candidate.location) || (isString(candidate.location) && candidate.location)) && (isUndefined(candidate.relativeLocation) || isString(candidate.relativeLocation)) && candidate.version && isString(candidate.version); } diff --git a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts index 0a86bed8233..579929a017f 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts @@ -414,7 +414,7 @@ suite('ExtensionsProfileScannerService', () => { test('read extension when manifest has empty lines and spaces', async () => { const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); - await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(` + await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(` `)); @@ -423,6 +423,25 @@ suite('ExtensionsProfileScannerService', () => { assert.deepStrictEqual(actual, []); }); + test('read extension when the relative location is empty', async () => { + const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); + const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); + await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(JSON.stringify([{ + identifier: extension.identifier, + location: extension.location.toJSON(), + relativeLocation: '', + version: extension.manifest.version, + }]))); + + const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + + const actual = await testObject.scanProfileExtensions(extensionsManifest); + assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [{ identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }]); + + const manifestContent = JSON.parse((await instantiationService.get(IFileService).readFile(extensionsManifest)).value.toString()); + assert.deepStrictEqual(manifestContent, [{ identifier: extension.identifier, location: extension.location.toJSON(), relativeLocation: 'pub.a-1.0.0', version: extension.manifest.version }]); + }); + function aExtension(id: string, location: URI, e?: Partial): IExtension { return { identifier: { id }, From 9236232f33137b56e5905e56b407648bab9840e3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 17:00:42 +0100 Subject: [PATCH 36/85] sandbox - fix exthost devtools URI --- .../extensions/electron-sandbox/localProcessExtensionHost.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index 132cf6090d3..d8a945776a4 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -287,7 +287,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { const inspectorUrlMatch = output.data && output.data.match(/ws:\/\/([^\s]+:(\d+)\/[^\s]+)/); if (inspectorUrlMatch) { if (!this._environmentService.isBuilt && !this._isExtensionDevTestFromCli) { - console.log(`%c[Extension Host] %cdebugger inspector at chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color:'); + console.log(`%c[Extension Host] %cdebugger inspector at devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color:'); } if (!this._inspectPort) { this._inspectPort = Number(inspectorUrlMatch[2]); From b958f349c48ab3d25db147883f7f5f2a594e1139 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 17:01:26 +0100 Subject: [PATCH 37/85] sandbox - make process explorer fit to show shared process when running as utility process --- src/vs/base/node/ps.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 4cf9ef29b94..cc01a30f0ba 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -48,12 +48,13 @@ export function listProcesses(rootPid: number): Promise { function findName(cmd: string): string { - const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/i; + const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/i; // TODO@bpasero remove me const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/i; const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/i; const UTILITY_NETWORK_HINT = /--utility-sub-type=network/i; const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/i; const UTILITY_FILE_WATCHER_HOST_HINT = /--vscode-utility-kind=fileWatcher/i; + const UTILITY_SHARED_PROCESS_HINT = /--vscode-utility-kind=shared-process/i; const WINDOWS_CRASH_REPORTER = /--crashes-directory/i; const WINDOWS_PTY = /\\pipe\\winpty-control/i; const WINDOWS_CONSOLE_HOST = /conhost\.exe/i; @@ -103,6 +104,10 @@ export function listProcesses(rootPid: number): Promise { if (UTILITY_FILE_WATCHER_HOST_HINT.exec(cmd)) { return 'file-watcher'; } + + if (UTILITY_SHARED_PROCESS_HINT.exec(cmd)) { + return 'shared-process'; + } } else if (matches[1] === 'extensionHost') { return 'extension-host'; // normalize remote extension host type } From 89907df37d8c60ccae7fc8c215bf47a323c5ccf6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 17:05:32 +0100 Subject: [PATCH 38/85] fix #173215 (#174466) --- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index df1301577f6..d96af56dc17 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -177,7 +177,7 @@ export class PromptExtensionInstallFailureAction extends Action { return undefined; } let targetPlatform = this.extension.gallery.properties.targetPlatform; - if (this.extensionManagementServerService.remoteExtensionManagementServer) { + if (targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNDEFINED && this.extensionManagementServerService.remoteExtensionManagementServer) { try { const manifest = await this.galleryService.getManifest(this.extension.gallery, CancellationToken.None); if (manifest && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(manifest)) { From a75e96b80474c9234a1a0cfe40fc2ea969f07887 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Feb 2023 17:06:07 +0100 Subject: [PATCH 39/85] sandbox - return message port from utility process --- .../electron-main/utilityProcess.ts | 49 +++++++++---------- .../utilityProcessWorkerMainService.ts | 4 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 1c80f0991a9..2d18ab8db5e 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, Details, MessageChannelMain, app } from 'electron'; +import { BrowserWindow, Details, MessageChannelMain, app, MessagePortMain } from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; @@ -23,6 +23,12 @@ export interface IUtilityProcessConfiguration { */ readonly type: string; + /** + * An optional serializable object to be sent into the utility process + * as first message alongside the message port. + */ + readonly payload?: unknown; + /** * Environment key-value pairs. Default is `process.env`. */ @@ -166,17 +172,11 @@ export class UtilityProcess extends Disposable { return true; } - start(configuration: IUtilityProcessConfiguration): boolean { + start(configuration: IUtilityProcessConfiguration): MessagePortMain | undefined { if (!this.validateCanStart()) { - return false; + return undefined; } - this.doStart(configuration); - - return true; - } - - protected doStart(configuration: IUtilityProcessConfiguration): UtilityProcessProposedApi.UtilityProcess { this.configuration = configuration; const serviceName = `${this.configuration.type}-${this.id}`; @@ -209,7 +209,11 @@ export class UtilityProcess extends Disposable { // Register to events this.registerListeners(this.process, this.configuration, serviceName); - return this.process; + // Establish message ports + const { port1: outPort, port2: utilityProcessPort } = new MessageChannelMain(); + this.process.postMessage(this.configuration.payload, [utilityProcessPort]); + + return outPort; } private registerListeners(process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { @@ -320,15 +324,14 @@ export class UtilityProcess extends Disposable { async waitForExit(maxWaitTimeMs: number): Promise { if (!this.process) { - this.log('no running process to wait for exit', Severity.Warning); - return; + return; // already killed, crashed or never started } this.log('waiting to exit...', Severity.Info); await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); if (this.process) { - this.log('did not exit within ${maxWaitTimeMs}ms, will kill it now...', Severity.Info); + this.log(`did not exit within ${maxWaitTimeMs}ms, will kill it now...`, Severity.Info); this.kill(); } } @@ -345,24 +348,27 @@ export class WindowUtilityProcess extends UtilityProcess { super(logService, telemetryService, lifecycleMainService); } - override start(configuration: IWindowUtilityProcessConfiguration): boolean { + override start(configuration: IWindowUtilityProcessConfiguration): MessagePortMain | undefined { const responseWindow = this.windowsMainService.getWindowById(configuration.responseWindowId)?.win; if (!responseWindow || responseWindow.isDestroyed() || responseWindow.webContents.isDestroyed()) { this.log('Refusing to start utility process because requesting window cannot be found or is destroyed...', Severity.Error); - return false; + return undefined; } // Start utility process - const process = super.doStart(configuration); + const messagePort = super.start(configuration); + if (!messagePort) { + return undefined; + } // Register to window events this.registerWindowListeners(responseWindow, configuration); // Exchange message ports to window - this.exchangeMessagePorts(process, configuration, responseWindow); + responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [messagePort]); - return true; + return messagePort; } private registerWindowListeners(window: BrowserWindow, configuration: IWindowUtilityProcessConfiguration): void { @@ -375,11 +381,4 @@ export class WindowUtilityProcess extends UtilityProcess { this._register(Event.fromNodeEventEmitter(window, 'closed')(() => this.kill())); } } - - private exchangeMessagePorts(process: UtilityProcessProposedApi.UtilityProcess, configuration: IWindowUtilityProcessConfiguration, responseWindow: BrowserWindow): void { - const { port1: windowPort, port2: utilityProcessPort } = new MessageChannelMain(); - - process.postMessage('null', [utilityProcessPort]); - responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [windowPort]); - } } diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts index 30848510d7f..cfb08c3fcfe 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts @@ -114,7 +114,7 @@ class UtilityProcessWorker extends Disposable { } spawn(): boolean { - return this.utilityProcess.start({ + const port = this.utilityProcess.start({ windowLifecycleBound: true, correlationId: `${this.configuration.reply.windowId}`, responseWindowId: this.configuration.reply.windowId, @@ -123,6 +123,8 @@ class UtilityProcessWorker extends Disposable { type: this.configuration.process.type, env: this.getEnv() }); + + return !!port; } private getEnv(): NodeJS.ProcessEnv { From ecebc66155c6eee93788e74c0311392578b8899e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Feb 2023 08:33:38 -0800 Subject: [PATCH 40/85] Fix compile --- src/vs/workbench/services/hover/browser/hoverService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index 66d5b00e83b..a6cd997618e 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -140,7 +140,7 @@ export class HoverService implements IHoverService { } } -function getHoverOptionsIdentity(options: IHoverOptions | undefined): IHoverOptions | number | undefined { +function getHoverOptionsIdentity(options: IHoverOptions | undefined): IHoverOptions | number | string | undefined { if (options === undefined) { return undefined; } From f861acbee1d4cf8cb19fbf4a415bfb97ab211df3 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 15 Feb 2023 08:35:09 -0800 Subject: [PATCH 41/85] Enable a tooltip in quick pick (#174417) * hover in quickpick * refactoring * fix tests * remove HTMLElement from API for now * API proposal --- .../browser/ui/iconLabel/iconHoverDelegate.ts | 29 +++++ .../platform/quickinput/browser/quickInput.ts | 8 ++ .../quickinput/browser/quickInputList.ts | 110 +++++++++++++++++- .../quickinput/browser/quickInputService.ts | 6 + .../platform/quickinput/common/quickInput.ts | 3 + .../test/browser/quickinput.test.ts | 6 + .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHostQuickOpen.ts | 32 +++-- .../common/extensionsApiProposals.ts | 1 + .../quickinput/browser/quickInputService.ts | 40 ++++++- .../test/browser/workbenchTestServices.ts | 22 +++- .../vscode.proposed.quickPickItemTooltip.d.ts | 16 +++ 12 files changed, 257 insertions(+), 18 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 1dd1e111bd9..e649c8f0c4a 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -14,11 +14,40 @@ export interface IHoverDelegateTarget extends IDisposable { } export interface IHoverDelegateOptions extends IUpdatableHoverOptions { + /** + * The content to display in the primary section of the hover. The type of text determines the + * default `hideOnHover` behavior. + */ content: IMarkdownString | string | HTMLElement; + /** + * The target for the hover. This determines the position of the hover and it will only be + * hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for + * simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a + * dispose method is required. + */ target: IHoverDelegateTarget | HTMLElement; + /** + * Position of the hover. The default is to show above the target. This option will be ignored + * if there is not enough room to layout the hover in the specified position, unless the + * forcePosition option is set. + */ hoverPosition?: HoverPosition; + /** + * Whether to show the hover pointer + */ showPointer?: boolean; + /** + * Whether to skip the fade in animation, this should be used when hovering from one hover to + * another in the same group so it looks like the hover is moving from one element to the other. + */ skipFadeInAnimation?: boolean; + /** + * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover + * in. This is particularly useful for more natural tab focusing behavior, where the hover is + * created as the next tab index after the element being hovered and/or to workaround the + * element's container hiding on `focusout`. + */ + container?: HTMLElement; } export interface IHoverDelegate { diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 798f6ccb2f6..8c1786f8654 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -9,6 +9,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -51,6 +52,7 @@ export interface IQuickInputOptions { renderers: IListRenderer[], options: IListOptions, ): List; + hoverDelegate: IHoverDelegate; styles: IQuickInputStyles; } @@ -1416,6 +1418,12 @@ export class QuickInputController extends Disposable { } } break; + case KeyCode.Space: + if (event.ctrlKey) { + dom.EventHelper.stop(e, true); + this.getUI().list.toggleHover(); + } + break; } })); diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index 30aea174bf6..650b9aa65ee 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -7,19 +7,23 @@ import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider, IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; import { IAction } from 'vs/base/common/actions'; import { range } from 'vs/base/common/arrays'; +import { ThrottledDelayer } from 'vs/base/common/async'; import { compareAnything } from 'vs/base/common/comparers'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { IMatch } from 'vs/base/common/filters'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { getCodiconAriaLabel, IParsedLabelWithIcons, matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { ltrim } from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -41,6 +45,7 @@ interface IListElement { readonly saneAriaLabel: string; readonly saneDescription?: string; readonly saneDetail?: string; + readonly saneTooltip?: string | IMarkdownString | HTMLElement; readonly labelHighlights?: IMatch[]; readonly descriptionHighlights?: IMatch[]; readonly detailHighlights?: IMatch[]; @@ -60,6 +65,8 @@ class ListElement implements IListElement, IDisposable { saneAriaLabel!: string; saneDescription?: string; saneDetail?: string; + saneTooltip?: string | IMarkdownString | HTMLElement; + element?: HTMLElement; hidden = false; private readonly _onChecked = new Emitter(); onChecked = this._onChecked.event; @@ -159,6 +166,7 @@ class ListElementRenderer implements IListRenderer { + // If we hover over an anchor element, we don't want to show the hover because + // the anchor may have a tooltip that we want to show instead. + if (e.browserEvent.target instanceof HTMLAnchorElement) { + delayer.cancel(); + return; + } + if ( + // anchors are an exception as called out above so we skip them here + !(e.browserEvent.relatedTarget instanceof HTMLAnchorElement) && + // check if the mouse is still over the same element + dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node) + ) { + return; + } + await delayer.trigger(async () => { + if (e.element) { + this.showHover(e.element); + } + }); + })); + this.disposables.push(this.list.onMouseOut(e => { + // onMouseOut triggers every time a new element has been moused over + // even if it's on the same list item. We only want one event, so we + // check if the mouse is still over the same element. + if (dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)) { + return; + } + delayer.cancel(); + })); this.disposables.push( this._onChangedAllVisibleChecked, this._onChangedCheckedCount, @@ -389,7 +433,8 @@ export class QuickInputList { this._onButtonTriggered, this._onSeparatorButtonTriggered, this._onLeave, - this._onKeyDown + this._onKeyDown, + delayer ); } @@ -475,7 +520,7 @@ export class QuickInputList { const saneLabel = item.label ? item.label.replace(/\r?\n/g, ' ') : ''; const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim(); - let saneMeta, saneDescription, saneDetail, labelHighlights, descriptionHighlights, detailHighlights; + let saneMeta, saneDescription, saneDetail, labelHighlights, descriptionHighlights, detailHighlights, saneTooltip; if (item.type !== 'separator') { saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' '); saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); @@ -483,6 +528,7 @@ export class QuickInputList { labelHighlights = item.highlights?.label; descriptionHighlights = item.highlights?.description; detailHighlights = item.highlights?.detail; + saneTooltip = item.tooltip; } const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail] .map(s => getCodiconAriaLabel(s)) @@ -512,6 +558,7 @@ export class QuickInputList { saneAriaLabel, saneDescription, saneDetail, + saneTooltip, labelHighlights, descriptionHighlights, detailHighlights, @@ -659,6 +706,30 @@ export class QuickInputList { this.list.domFocus(); } + /** + * Disposes of the hover and shows a new one for the given index if it has a tooltip. + * @param element The element to show the hover for + */ + private showHover(element: ListElement): void { + if (this._lastHover && !this._lastHover.isDisposed) { + this.options.hoverDelegate.onDidHideHover?.(); + this._lastHover?.dispose(); + } + if (!element.element || !element.saneTooltip) { + return; + } + this._lastHover = this.options.hoverDelegate.showHover({ + content: element.saneTooltip!, + target: element.element!, + linkHandler: (url) => { + this.options.linkOpenerDelegate(url); + }, + showPointer: true, + container: this.container, + hoverPosition: HoverPosition.RIGHT + }, false); + } + layout(maxHeight?: number): void { this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : ''; this.list.layout(); @@ -795,6 +866,37 @@ export class QuickInputList { style(styles: IListStyles) { this.list.style(styles); } + + toggleHover() { + const element = this.list.getFocusedElements()[0]; + if (!element.saneTooltip) { + return; + } + + // if there's a hover already, hide it (toggle off) + if (this._lastHover && !this._lastHover.isDisposed) { + this._lastHover.dispose(); + return; + } + + // If there is no hover, show it (toggle on) + const focused = this.list.getFocusedElements()[0]; + if (!focused) { + return; + } + this.showHover(focused); + const store = new DisposableStore(); + store.add(this.list.onDidChangeFocus(e => { + if (e.indexes.length) { + this.showHover(e.elements[0]); + } + })); + if (this._lastHover) { + store.add(this._lastHover); + } + this._toggleHover = store; + this.elementDisposables.push(this._toggleHover); + } } function matchesContiguousIconAware(query: string, target: IParsedLabelWithIcons): IMatch[] | null { diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index 90b8ed4ef38..5e7473475d7 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -89,6 +89,12 @@ export class QuickInputService extends Themable implements IQuickInputService { renderers: IListRenderer[], options: IWorkbenchListOptions ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, + hoverDelegate: { + showHover(options, focus) { + return undefined; + }, + delay: 200 + }, styles: this.computeStyles() }; diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index ff260f92f56..7e3bd52dd2d 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -14,6 +14,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; export interface IQuickPickItemHighlights { label?: IMatch[]; @@ -31,6 +32,7 @@ export interface IQuickPickItem { ariaLabel?: string; description?: string; detail?: string; + tooltip?: string | IMarkdownString; /** * Allows to show a keybinding next to the item to indicate * how the item can be triggered outside of the picker using @@ -52,6 +54,7 @@ export interface IQuickPickSeparator { label?: string; ariaLabel?: string; buttons?: readonly IQuickInputButton[]; + tooltip?: string | IMarkdownString; } export interface IKeyMods { diff --git a/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/src/vs/platform/quickinput/test/browser/quickinput.test.ts index 5e74d3548bf..34a0547388b 100644 --- a/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -58,6 +58,12 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 renderers: IListRenderer[], options: IListOptions, ) => new List(user, container, delegate, renderers, options), + hoverDelegate: { + showHover(options, focus) { + return undefined; + }, + delay: 200 + }, styles: { button: unthemedButtonStyles, countBadge: unthemedCountStyles, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 27edca9fa34..8886bf9d058 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -670,7 +670,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return >extHostMessageService.showMessage(extension, Severity.Error, message, rest[0], >rest.slice(1)); }, showQuickPick(items: any, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): any { - return extHostQuickOpen.showQuickPick(items, options, token); + return extHostQuickOpen.showQuickPick(extension, items, options, token); }, showWorkspaceFolderPick(options?: vscode.WorkspaceFolderPickOptions) { return extHostQuickOpen.showWorkspaceFolderPick(options); diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 4ae22adf430..558c61f22fb 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -17,14 +17,16 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { coalesce } from 'vs/base/common/arrays'; import Severity from 'vs/base/common/severity'; import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters'; export type Item = string | QuickPickItem; export interface ExtHostQuickOpen { - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: string[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: Item[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: string[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: Item[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; showInput(options?: InputBoxOptions, token?: CancellationToken): Promise; @@ -55,11 +57,10 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._commands = commands; } - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: string[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: Item[] | Promise, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise { - + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: string[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: Item[] | Promise, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise { // clear state from last invocation this._onDidSelectItem = undefined; @@ -84,6 +85,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx return undefined; } + const allowedTooltips = isProposedApiEnabled(extension, 'quickPickItemTooltip'); + return itemsPromise.then(items => { const pickItems: TransferQuickPickItemOrSeparator[] = []; @@ -94,12 +97,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } else if (item.kind === QuickPickItemKind.Separator) { pickItems.push({ type: 'separator', label: item.label }); } else { + if (item.tooltip && !allowedTooltips) { + console.warn(`Extension '${extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`); + } pickItems.push({ label: item.label, description: item.description, detail: item.detail, picked: item.picked, alwaysShow: item.alwaysShow, + tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined, handle }); } @@ -535,7 +542,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private readonly _onDidChangeSelectionEmitter = new Emitter(); private readonly _onDidTriggerItemButtonEmitter = new Emitter>(); - constructor(extension: IExtensionDescription, onDispose: () => void) { + constructor(private extension: IExtensionDescription, onDispose: () => void) { super(extension.identifier, onDispose); this._disposables.push( this._onDidChangeActiveEmitter, @@ -558,12 +565,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._itemsToHandles.set(item, i); }); + const allowedTooltips = isProposedApiEnabled(this.extension, 'quickPickItemTooltip'); const pickItems: TransferQuickPickItemOrSeparator[] = []; for (let handle = 0; handle < items.length; handle++) { const item = items[handle]; if (item.kind === QuickPickItemKind.Separator) { pickItems.push({ type: 'separator', label: item.label }); } else { + if (item.tooltip && !allowedTooltips) { + console.warn(`Extension '${this.extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this.extension.identifier.value}`); + } pickItems.push({ handle, label: item.label, @@ -571,6 +582,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx detail: item.detail, picked: item.picked, alwaysShow: item.alwaysShow, + tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined, buttons: item.buttons?.map((button, i) => { return { ...getIconPathOrClass(button), diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 8929645d6dd..9f06058245a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -51,6 +51,7 @@ export const allApiProposals = Object.freeze({ portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', profileContentHandlers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts', quickDiffProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts', + quickPickItemTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index 95122549d9f..77498318db2 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -15,9 +15,12 @@ import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinp import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { InQuickPickContextKey } from 'vs/workbench/browser/quickaccess'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export class QuickInputService extends BaseQuickInputService { + private readonly hoverDelegate = new QuickInputHoverDelegate(this.configurationService, this.hoverService); private readonly inQuickInputContext = InQuickPickContextKey.bindTo(this.contextKeyService); constructor( @@ -27,7 +30,8 @@ export class QuickInputService extends BaseQuickInputService { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @IAccessibilityService accessibilityService: IAccessibilityService, - @ILayoutService layoutService: ILayoutService + @ILayoutService layoutService: ILayoutService, + @IHoverService private readonly hoverService: IHoverService ) { super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); @@ -42,9 +46,41 @@ export class QuickInputService extends BaseQuickInputService { protected override createController(): QuickInputController { return super.createController(this.layoutService, { ignoreFocusOut: () => !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'), - backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined + backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined, + hoverDelegate: this.hoverDelegate }); } } +class QuickInputHoverDelegate implements IHoverDelegate { + private lastHoverHideTime = 0; + readonly placement = 'element'; + + get delay() { + if (Date.now() - this.lastHoverHideTime < 200) { + return 0; // show instantly when a hover was recently shown + } + + return this.configurationService.getValue('workbench.hover.delay'); + } + + constructor( + private readonly configurationService: IConfigurationService, + private readonly hoverService: IHoverService + ) { } + + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + return this.hoverService.showHover({ + ...options, + hideOnHover: false, + hideOnKeyDown: false, + skipFadeInAnimation: true, + }, focus); + } + + onDidHideHover(): void { + this.lastHoverHideTime = Date.now(); + } +} + registerSingleton(IQuickInputService, QuickInputService, InstantiationType.Delayed); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f3bc012c9d8..cbae6e46829 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -165,6 +165,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; +import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { @@ -317,7 +318,8 @@ export function workbenchInstantiationService( instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService))); instantiationService.stub(IPaneCompositePartService, new TestPaneCompositeService()); instantiationService.stub(IListService, new TestListService()); - instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService))); + const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService)); + instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService, hoverService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); @@ -730,6 +732,24 @@ export class TestSideBarPart implements IPaneCompositePart { layout(width: number, height: number, top: number, left: number): void { } } +class TestHoverService implements IHoverService { + private currentHover: IHoverWidget | undefined; + _serviceBrand: undefined; + showHover(options: IHoverOptions, focus?: boolean | undefined): IHoverWidget | undefined { + this.currentHover = new class implements IHoverWidget { + private _isDisposed = false; + get isDisposed(): boolean { return this._isDisposed; } + dispose(): void { + this._isDisposed = true; + } + }; + return this.currentHover; + } + hideHover(): void { + this.currentHover?.dispose(); + } +} + export class TestPanelPart implements IPaneCompositePart, IPaneCompositeSelectorPart { declare readonly _serviceBrand: undefined; diff --git a/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts b/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts new file mode 100644 index 00000000000..4e7d00fa5ed --- /dev/null +++ b/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/73904 + + export interface QuickPickItem { + /** + * An optional flag to sort the final results by index of first query match in label. Defaults to true. + */ + tooltip?: string | MarkdownString; + } +} From eb940a296f0f32f859907def1fa87f62418a4347 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 19:27:10 +0100 Subject: [PATCH 42/85] add border to profile badge (#174477) --- .../workbench/browser/parts/activitybar/media/activityaction.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 1303822108e..83cc1b469fa 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -183,6 +183,7 @@ border-radius: 3px; background-color: var(--vscode-activityBar-background); color: var(--vscode-activityBar-inactiveForeground); + border: 1px solid; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:active .profile-badge-content, From 4547f4b5dec5ee55b5dc168ea96001f58e30cae1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2023 20:37:19 +0100 Subject: [PATCH 43/85] add more logging (#174481) * add more logging * fix tests --- .../common/extensionsProfileScannerService.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index e73fe13aa14..d55c40f3611 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -14,12 +14,13 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IExtension, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { Mutable, isObject, isString, isUndefined } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Schemas } from 'vs/base/common/network'; interface IStoredProfileExtension { identifier: IExtensionIdentifier; @@ -309,9 +310,14 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable } private toRelativePath(extensionLocation: URI): string | undefined { - return this.uriIdentityService.extUri.isEqualOrParent(extensionLocation, this.extensionsLocation) - ? this.uriIdentityService.extUri.relativePath(this.extensionsLocation, extensionLocation) - : undefined; + if (this.uriIdentityService.extUri.isEqualOrParent(extensionLocation, this.extensionsLocation)) { + const relativePath = this.uriIdentityService.extUri.relativePath(this.extensionsLocation, extensionLocation); + if (this.extensionsLocation.scheme === Schemas.file && this.logService.getLevel() === LogLevel.Trace) { + this.logService.trace('Relative path', extensionLocation.fsPath, this.extensionsLocation.fsPath, relativePath); + } + return relativePath; + } + return undefined; } private resolveExtensionLocation(path: string): URI { From 9570a06456df24893243955150a10908ba5e9884 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 15 Feb 2023 11:55:31 -0800 Subject: [PATCH 44/85] Fix #174393. Clear error container. (#174494) --- extensions/notebook-renderers/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index f24776d1624..9220ff0c79f 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -135,6 +135,8 @@ async function renderJavascript(outputInfo: OutputItem, container: HTMLElement, } function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext & { readonly settings: RenderOptions }): void { + clearContainer(container); + const element = document.createElement('div'); container.appendChild(element); type ErrorLike = Partial; From c65c811f1e6840f93c98787225752cf3f6961a39 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 15 Feb 2023 13:44:04 -0700 Subject: [PATCH 45/85] Mimic status bar hover delay in explorer (#174497) Implement status bar delay --- .../contrib/files/browser/views/explorerViewer.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 709f03e5948..bca1c18ab2a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -280,9 +280,15 @@ export class FilesRenderer implements ICompressibleTreeRenderer('workbench.hover.delay'); } @@ -334,6 +340,10 @@ export class FilesRenderer implements ICompressibleTreeRenderer Date: Wed, 15 Feb 2023 14:21:48 -0700 Subject: [PATCH 46/85] Use !important to prevent hover widget overriding hover size (#174503) Fixes #174498 --- .../workbench/contrib/files/browser/media/explorerviewlet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index ba3fc0686f6..ad60aa1aba4 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -14,7 +14,7 @@ cursor: pointer !important; padding-left: 6px; height: 22px; - font-size: 13px; + font-size: 13px !important; } .explorer-folders-view .monaco-list-row { From dc619de8d9efdabd781fc5658d0051813c347055 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 15 Feb 2023 22:34:14 +0100 Subject: [PATCH 47/85] Icons in problems view show black when not focused (#174487) * Icons in problems view show black when not focused * update smoke test --- .../severityIcon/browser/media/severityIcon.css | 6 +++--- .../contrib/markers/browser/markersTreeViewer.ts | 12 ++++++++++-- .../workbench/contrib/markers/browser/markersView.ts | 3 +-- test/automation/src/problems.ts | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/severityIcon/browser/media/severityIcon.css b/src/vs/platform/severityIcon/browser/media/severityIcon.css index 06681b4c2b8..4fd1d06c80b 100644 --- a/src/vs/platform/severityIcon/browser/media/severityIcon.css +++ b/src/vs/platform/severityIcon/browser/media/severityIcon.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .zone-widget .codicon.codicon-error, -.markers-panel .marker-icon.codicon.codicon-error, +.markers-panel .marker-icon.error, .markers-panel .marker-icon .codicon.codicon-error, .text-search-provider-messages .providerMessage .codicon.codicon-error, .extensions-viewlet > .extensions .codicon.codicon-error, .extension-editor .codicon.codicon-error, @@ -13,7 +13,7 @@ } .monaco-editor .zone-widget .codicon.codicon-warning, -.markers-panel .marker-icon.codicon.codicon-warning, +.markers-panel .marker-icon.warning, .markers-panel .marker-icon .codicon.codicon-warning, .text-search-provider-messages .providerMessage .codicon.codicon-warning, .extensions-viewlet > .extensions .codicon.codicon-warning, .extension-editor .codicon.codicon-warning, @@ -22,7 +22,7 @@ } .monaco-editor .zone-widget .codicon.codicon-info, -.markers-panel .marker-icon.codicon.codicon-info, +.markers-panel .marker-icon.info, .markers-panel .marker-icon .codicon.codicon-info, .text-search-provider-messages .providerMessage .codicon.codicon-info, .extensions-viewlet > .extensions .codicon.codicon-info, .extension-editor .codicon.codicon-info, diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 84fd6ede8b1..9c6c440c71c 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -50,6 +50,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { MarkersContextKeys, MarkersViewMode } from 'vs/workbench/contrib/markers/common/markers'; import { unsupportedSchemas } from 'vs/platform/markers/common/markerService'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import Severity from 'vs/base/common/severity'; interface IResourceMarkersTemplateData { readonly resourceLabel: IResourceLabel; @@ -270,6 +271,7 @@ class MarkerWidget extends Disposable { private readonly actionBar: ActionBar; private readonly icon: HTMLElement; + private readonly iconContainer: HTMLElement; private readonly messageAndDetailsContainer: HTMLElement; private readonly disposables = this._register(new DisposableStore()); @@ -283,7 +285,12 @@ class MarkerWidget extends Disposable { this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); - this.icon = dom.append(parent, dom.$('')); + + // wrap the icon in a container that get the icon color as foreground color. That way, if the + // list view does not have a specific color for the icon (=the color variable is invalid) it + // falls back to the foreground color of container (inherit) + this.iconContainer = dom.append(parent, dom.$('')); + this.icon = dom.append(this.iconContainer, dom.$('')); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details-container')); } @@ -292,7 +299,8 @@ class MarkerWidget extends Disposable { this.disposables.clear(); dom.clearNode(this.messageAndDetailsContainer); - this.icon.className = `marker-icon codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`; + this.iconContainer.className = `marker-icon ${Severity.toString(MarkerSeverity.toSeverity(element.marker.severity))}`; + this.icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`; this.renderQuickfixActionbar(element); this.renderMessageAndDetails(element, filterData); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index e41c8f3b3b8..16f60bdc2e8 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -478,8 +478,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { }), expandOnlyOnTwistieClick: (e: MarkerElement) => e instanceof Marker && e.relatedInformation.length > 0, overrideStyles: { - listBackground: this.getBackgroundColor(), - listInactiveSelectionIconForeground: undefined // we don't want to override the severity icon color + listBackground: this.getBackgroundColor() }, selectionNavigation: true, multipleSelectionSupport: true, diff --git a/test/automation/src/problems.ts b/test/automation/src/problems.ts index 0527b50d767..026ccf4b061 100644 --- a/test/automation/src/problems.ts +++ b/test/automation/src/problems.ts @@ -33,7 +33,7 @@ export class Problems { static getSelectorInProblemsView(problemType: ProblemSeverity): string { const selector = problemType === ProblemSeverity.WARNING ? 'codicon-warning' : 'codicon-error'; - return `div[id="workbench.panel.markers"] .monaco-tl-contents .marker-icon.${selector}`; + return `div[id="workbench.panel.markers"] .monaco-tl-contents .marker-icon .${selector}`; } static getSelectorInEditor(problemType: ProblemSeverity): string { From 2d447788b504486724484f5d8f377c98e3d671ac Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Feb 2023 05:54:48 +0100 Subject: [PATCH 48/85] sandbox - add `once` method for message ports --- src/vs/base/parts/ipc/node/ipc.mp.ts | 11 +++++++++++ src/vs/code/electron-main/app.ts | 3 +-- .../sharedProcess/electron-main/sharedProcess.ts | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.mp.ts b/src/vs/base/parts/ipc/node/ipc.mp.ts index 1a1f8330251..fcee18c11ae 100644 --- a/src/vs/base/parts/ipc/node/ipc.mp.ts +++ b/src/vs/base/parts/ipc/node/ipc.mp.ts @@ -67,3 +67,14 @@ export class Server extends IPCServer { super(Server.getOnDidClientConnect()); } } + +export function once(port: MessagePortMain, message: unknown, callback: Function): void { + const listener = (e: MessageEvent) => { + if (e.data === message) { + port.removeListener('message', listener); + callback(); + } + }; + + port.on('message', listener); +} diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c80ccdc8ea4..8bc4ff7f891 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -213,9 +213,8 @@ export class CodeApplication extends Disposable { }; const isAllowedWebviewRequest = (uri: URI, details: Electron.OnBeforeRequestListenerDetails): boolean => { - // Only restrict top level page of webviews: index.html if (uri.path !== '/index.html') { - return true; + return true; // Only restrict top level page of webviews: index.html } const frame = details.frame; diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 46fca623948..8c8c0b16f27 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -76,6 +76,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { // await the shared process to be overall ready // we do not just wait for IPC ready because the // workbench window will communicate directly + await this.whenReady(); // connect to the shared process window @@ -85,6 +86,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { // Since shared process is delayed on startup there is // a chance that the window close before the shared process // was ready for a connection. + if (e.sender.isDestroyed()) { return port.close(); } @@ -167,7 +169,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { private send(channel: string, ...args: any[]): void { if (!this.isAlive()) { - this.logService.warn(`Sending IPC message to channel '${channel}' for shared process window that is destroyed`); + this.logService.warn(`Sending IPC message to channel '${channel}' for shared process that is destroyed`); return; } From 0b42769497644653bb0c9e4e2ad85de7365b7dfc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Feb 2023 06:22:38 +0100 Subject: [PATCH 49/85] sandbox - more leverage of existing `parentPort` --- src/vs/base/parts/ipc/node/ipc.mp.ts | 7 ++- .../electron-main/utilityProcess.ts | 62 ++++++++++++++----- .../utilityProcessWorkerMainService.ts | 4 +- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.mp.ts b/src/vs/base/parts/ipc/node/ipc.mp.ts index fcee18c11ae..112ed3c74dd 100644 --- a/src/vs/base/parts/ipc/node/ipc.mp.ts +++ b/src/vs/base/parts/ipc/node/ipc.mp.ts @@ -68,7 +68,12 @@ export class Server extends IPCServer { } } -export function once(port: MessagePortMain, message: unknown, callback: Function): void { +interface INodeMessagePortFragment { + on(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this; +} + +export function once(port: INodeMessagePortFragment, message: unknown, callback: () => void): void { const listener = (e: MessageEvent) => { if (e.data === message) { port.removeListener('message', listener); diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 2d18ab8db5e..44e8d0c5984 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, Details, MessageChannelMain, app, MessagePortMain } from 'electron'; +import { BrowserWindow, Details, app, MessageChannelMain } from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; @@ -172,9 +172,19 @@ export class UtilityProcess extends Disposable { return true; } - start(configuration: IUtilityProcessConfiguration): MessagePortMain | undefined { + start(configuration: IUtilityProcessConfiguration): boolean { + const started = this.doStart(configuration); + + if (started && configuration.payload) { + this.postMessage(configuration.payload); + } + + return started; + } + + protected doStart(configuration: IUtilityProcessConfiguration): boolean { if (!this.validateCanStart()) { - return undefined; + return false; } this.configuration = configuration; @@ -209,11 +219,7 @@ export class UtilityProcess extends Disposable { // Register to events this.registerListeners(this.process, this.configuration, serviceName); - // Establish message ports - const { port1: outPort, port2: utilityProcessPort } = new MessageChannelMain(); - this.process.postMessage(this.configuration.payload, [utilityProcessPort]); - - return outPort; + return true; } private registerListeners(process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { @@ -230,7 +236,7 @@ export class UtilityProcess extends Disposable { this._register(Event.fromNodeEventEmitter(process.stderr, 'data')(chunk => this._onStderr.fire(typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk)))); } - //Messages + // Messages this._register(Event.fromNodeEventEmitter(process, 'message')(msg => this._onMessage.fire(msg))); // Spawn @@ -280,6 +286,24 @@ export class UtilityProcess extends Disposable { })); } + once(message: unknown, callback: () => void): void { + const disposable = this._register(this._onMessage.event(msg => { + if (msg === message) { + disposable.dispose(); + + callback(); + } + })); + } + + postMessage(message: unknown, transfer?: Electron.MessagePortMain[]): void { + if (!this.process) { + return; // already killed, crashed or never started + } + + this.process.postMessage(message, transfer); + } + enableInspectPort(): boolean { if (!this.process || typeof this.processPid !== 'number') { return false; @@ -348,27 +372,31 @@ export class WindowUtilityProcess extends UtilityProcess { super(logService, telemetryService, lifecycleMainService); } - override start(configuration: IWindowUtilityProcessConfiguration): MessagePortMain | undefined { + override start(configuration: IWindowUtilityProcessConfiguration): boolean { const responseWindow = this.windowsMainService.getWindowById(configuration.responseWindowId)?.win; if (!responseWindow || responseWindow.isDestroyed() || responseWindow.webContents.isDestroyed()) { this.log('Refusing to start utility process because requesting window cannot be found or is destroyed...', Severity.Error); - return undefined; + return true; } // Start utility process - const messagePort = super.start(configuration); - if (!messagePort) { - return undefined; + const started = super.doStart(configuration); + if (!started) { + return false; } // Register to window events this.registerWindowListeners(responseWindow, configuration); - // Exchange message ports to window - responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [messagePort]); + // Establish message ports + const { port1: windowPort, port2: utilityProcessPort } = new MessageChannelMain(); + this.postMessage(configuration.payload, [utilityProcessPort]); - return messagePort; + // Exchange message ports to window + responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [windowPort]); + + return true; } private registerWindowListeners(window: BrowserWindow, configuration: IWindowUtilityProcessConfiguration): void { diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts index cfb08c3fcfe..30848510d7f 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts @@ -114,7 +114,7 @@ class UtilityProcessWorker extends Disposable { } spawn(): boolean { - const port = this.utilityProcess.start({ + return this.utilityProcess.start({ windowLifecycleBound: true, correlationId: `${this.configuration.reply.windowId}`, responseWindowId: this.configuration.reply.windowId, @@ -123,8 +123,6 @@ class UtilityProcessWorker extends Disposable { type: this.configuration.process.type, env: this.getEnv() }); - - return !!port; } private getEnv(): NodeJS.ProcessEnv { From fb256581df0c63f5b9274fb99cf59bb496523f05 Mon Sep 17 00:00:00 2001 From: John Murray Date: Thu, 16 Feb 2023 06:51:39 +0000 Subject: [PATCH 50/85] Point comment zone widget 'beak' arrow at single-line range (#173690) (#174291) * Point comment zone widget 'beak' arrow at single-line range (#173690) * Remove unused variable --- .../browser/commentThreadZoneWidget.ts | 29 ++++++++++--------- .../comments/browser/commentsController.ts | 4 +-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 26e54d5d898..882b3371628 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -175,7 +175,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget public reveal(commentUniqueId?: number, focus: boolean = false) { if (!this._isExpanded) { - this.show({ lineNumber: this._commentThread.range.endLineNumber, column: 1 }, 2); + this.show(this.arrowPosition(this._commentThread.range), 2); } if (commentUniqueId !== undefined) { @@ -242,6 +242,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._disposables.add(this._commentThreadWidget); } + private arrowPosition(range: IRange): IPosition { + // Arrow on top edge of zone widget will be at the start of the line if range is multi-line, else at midpoint of range (rounding rightwards) + return { lineNumber: range.endLineNumber, column: range.endLineNumber === range.startLineNumber ? (range.startColumn + range.endColumn + 1) / 2 : 1 }; + } + private deleteCommentThread(): void { this.dispose(); this.commentService.disposeCommentThread(this.owner, this._commentThread.threadId); @@ -260,9 +265,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget public expand(): Promise { this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; - const lineNumber = this._commentThread.range.endLineNumber; - this.show({ lineNumber, column: 1 }, 2); + this.show(this.arrowPosition(this._commentThread.range), 2); return Promise.resolve(); } @@ -273,7 +277,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget return 0; } - toggleExpand(lineNumber: number) { + toggleExpand() { if (this._isExpanded) { this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Collapsed; this.hide(); @@ -282,7 +286,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } else { this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; - this.show({ lineNumber: lineNumber, column: 1 }, 2); } } @@ -308,11 +311,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } if (shouldMoveWidget && this._isExpanded) { - this.show({ lineNumber, column: 1 }, 2); + this.show(this.arrowPosition(this._commentThread.range), 2); } if (this._commentThread.collapsibleState === languages.CommentThreadCollapsibleState.Expanded) { - this.show({ lineNumber, column: 1 }, 2); + this.show(this.arrowPosition(this._commentThread.range), 2); } else { this.hide(); } @@ -326,8 +329,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadWidget.layout(widthInPixel); } - display(lineNumber: number) { - this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber); + display(range: IRange) { + this._commentGlyph = new CommentGlyphWidget(this.editor, range.endLineNumber); this._commentGlyph.setThreadState(this._commentThread.state); this._commentThreadWidget.display(this.editor.getOption(EditorOption.lineHeight)); @@ -335,7 +338,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._refresh(dimension); })); if (this._commentThread.collapsibleState === languages.CommentThreadCollapsibleState.Expanded) { - this.show({ lineNumber: lineNumber, column: 1 }, 2); + this.show(this.arrowPosition(range), 2); } // If this is a new comment thread awaiting user input then we need to reveal it. @@ -363,15 +366,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } if (shouldMoveWidget && this._isExpanded) { - this.show({ lineNumber, column: 1 }, 2); + this.show(this.arrowPosition(this._commentThread.range), 2); } })); this._commentThreadDisposables.push(this._commentThread.onDidChangeCollapsibleState(state => { if (state === languages.CommentThreadCollapsibleState.Expanded && !this._isExpanded) { - const lineNumber = this._commentThread.range.startLineNumber; - - this.show({ lineNumber, column: 1 }, 2); + this.show(this.arrowPosition(this._commentThread.range), 2); return; } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 60ffd13cac1..3396c53ee4d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -709,7 +709,7 @@ export class CommentController implements IEditorContribution { return; } const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment); - zoneWidget.display(thread.range.endLineNumber); + zoneWidget.display(thread.range); this._commentWidgets.push(zoneWidget); this.openCommentsView(thread); } @@ -762,7 +762,7 @@ export class CommentController implements IEditorContribution { // The widget's position is undefined until the widget has been displayed, so rely on the glyph position instead const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === commentRange.endLineNumber); if (existingCommentsAtLine.length) { - existingCommentsAtLine.forEach(widget => widget.toggleExpand(commentRange.endLineNumber)); + existingCommentsAtLine.forEach(widget => widget.toggleExpand()); this.processNextThreadToAdd(); return; } else { From 5dc11382163eea3db30a973957a449fc54e03c85 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Feb 2023 08:55:32 +0100 Subject: [PATCH 51/85] sandbox - first cut utility process support for shared process --- src/vs/base/parts/ipc/node/ipc.mp.ts | 7 +- .../sharedProcess/sharedProcessMain.ts | 97 +++++-- .../electron-main/sharedProcess.ts | 244 ++++++++++++------ .../electron-main/utilityProcess.ts | 16 +- 4 files changed, 243 insertions(+), 121 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.mp.ts b/src/vs/base/parts/ipc/node/ipc.mp.ts index 112ed3c74dd..a7cfc538d5e 100644 --- a/src/vs/base/parts/ipc/node/ipc.mp.ts +++ b/src/vs/base/parts/ipc/node/ipc.mp.ts @@ -8,6 +8,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { Emitter, Event } from 'vs/base/common/event'; import { assertType } from 'vs/base/common/types'; +import { firstOrDefault } from 'vs/base/common/arrays'; /** * The MessagePort `Protocol` leverages MessagePortMain style IPC communication @@ -44,8 +45,10 @@ export class Server extends IPCServer { const onCreateMessageChannel = new Emitter(); process.parentPort.on('message', (e: Electron.MessageEvent) => { - const ports = e.ports; - onCreateMessageChannel.fire(ports[0]); + const port = firstOrDefault(e.ports); + if (port) { + onCreateMessageChannel.fire(port); + } }); return Event.map(onCreateMessageChannel.event, port => { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index ebf2ba99577..ddfb1bc888c 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcRenderer } from 'electron'; import { hostname, release } from 'os'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; @@ -11,8 +10,9 @@ import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lif import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; +import { IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { Server as BrowserWindowMessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; +import { Server as UtilityProcessMessagePortServer, once } from 'vs/base/parts/ipc/node/ipc.mp'; import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner'; import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater'; @@ -117,18 +117,25 @@ import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement import { localize } from 'vs/nls'; import { LogService } from 'vs/platform/log/common/logService'; import { ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerConfiguration } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService'; +import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes'; class SharedProcessMain extends Disposable { - private server = this._register(new MessagePortServer()); + private readonly server: IPCServer; private sharedProcessWorkerService: ISharedProcessWorkerService | undefined = undefined; private lifecycleService: SharedProcessLifecycleService | undefined = undefined; - constructor(private configuration: ISharedProcessConfiguration) { + constructor(private configuration: ISharedProcessConfiguration, private ipcRenderer?: typeof import('electron').ipcRenderer) { super(); + if (isUtilityProcess(process)) { + this.server = this._register(new UtilityProcessMessagePortServer()); + } else { + this.server = this._register(new BrowserWindowMessagePortServer()); + } + this.registerListeners(); } @@ -140,25 +147,32 @@ class SharedProcessMain extends Disposable { this.dispose(); }; process.once('exit', onExit); - ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit); + if (isUtilityProcess(process)) { + once(process.parentPort, 'vscode:electron-main->shared-process=exit', onExit); + } else { + this.ipcRenderer!.once('vscode:electron-main->shared-process=exit', onExit); + } - // Shared process worker lifecycle - // - // We dispose the listener when the shared process is - // disposed to avoid disposing workers when the entire - // application is shutting down anyways. - // - const eventName = 'vscode:electron-main->shared-process=disposeWorker'; - const onDisposeWorker = (event: unknown, configuration: IUtilityProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); }; - ipcRenderer.on(eventName, onDisposeWorker); - this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker))); + if (!isUtilityProcess(process)) { + + // Shared process worker lifecycle + // + // We dispose the listener when the shared process is + // disposed to avoid disposing workers when the entire + // application is shutting down anyways. + + const eventName = 'vscode:electron-main->shared-process=disposeWorker'; + const onDisposeWorker = (event: unknown, configuration: IUtilityProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); }; + this.ipcRenderer!.on(eventName, onDisposeWorker); + this._register(toDisposable(() => this.ipcRenderer!.removeListener(eventName, onDisposeWorker))); + } } private onDisposeWorker(configuration: IUtilityProcessWorkerConfiguration): void { this.sharedProcessWorkerService?.disposeWorker(configuration); } - async open(): Promise { + async init(): Promise { // Services const instantiationService = await this.initServices(); @@ -456,15 +470,20 @@ class SharedProcessMain extends Disposable { private registerErrorHandler(logService: ILogService): void { - // Listen on unhandled rejection events - window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { + // Listen on global error events + if (isUtilityProcess(process)) { + process.on('uncaughtException', error => onUnexpectedError(error)); + process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); + } else { + window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { - // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent - onUnexpectedError(event.reason); + // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent + onUnexpectedError(event.reason); - // Prevent the printing of this event to the console - event.preventDefault(); - }); + // Prevent the printing of this event to the console + event.preventDefault(); + }); + } // Install handler for unexpected errors setUnexpectedErrorHandler(error => { @@ -482,10 +501,32 @@ export async function main(configuration: ISharedProcessConfiguration): Promise< // create shared process and signal back to main that we are // ready to accept message ports as client connections - const sharedProcess = new SharedProcessMain(configuration); - ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready'); + + let ipcRenderer: typeof import('electron').ipcRenderer | undefined = undefined; + if (!isUtilityProcess(process)) { + ipcRenderer = (await import('electron')).ipcRenderer; + } + + const sharedProcess = new SharedProcessMain(configuration, ipcRenderer); + + if (isUtilityProcess(process)) { + process.parentPort.postMessage('vscode:shared-process->electron-main=ipc-ready'); + } else { + ipcRenderer!.send('vscode:shared-process->electron-main=ipc-ready'); + } // await initialization and signal this back to electron-main - await sharedProcess.open(); - ipcRenderer.send('vscode:shared-process->electron-main=init-done'); + await sharedProcess.init(); + + if (isUtilityProcess(process)) { + process.parentPort.postMessage('vscode:shared-process->electron-main=init-done'); + } else { + ipcRenderer!.send('vscode:shared-process->electron-main=init-done'); + } +} + +if (isUtilityProcess(process)) { + process.parentPort.once('message', (e: Electron.MessageEvent) => { + main(e.data as ISharedProcessConfiguration); + }); } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 8c8c0b16f27..03ff3f3835f 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -5,7 +5,7 @@ import { BrowserWindow, Event as ElectronEvent, IpcMainEvent, MessagePortMain } from 'electron'; import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; -import { Barrier } from 'vs/base/common/async'; +import { Barrier, DeferredPromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; @@ -25,16 +25,24 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; +import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { deepClone } from 'vs/base/common/objects'; +import { removeDangerousEnvVariables } from 'vs/base/common/processes'; export class SharedProcess extends Disposable implements ISharedProcess { + private readonly _onDidError = this._register(new Emitter<{ type: WindowError; details?: { reason: string; exitCode: number } }>()); + readonly onDidError = Event.buffer(this._onDidError.event); // buffer until we have a listener! + private readonly firstWindowConnectionBarrier = new Barrier(); private window: BrowserWindow | undefined = undefined; private windowCloseListener: ((event: ElectronEvent) => void) | undefined = undefined; - private readonly _onDidError = this._register(new Emitter<{ type: WindowError; details?: { reason: string; exitCode: number } }>()); - readonly onDidError = Event.buffer(this._onDidError.event); // buffer until we have a listener! + private utilityProcess: UtilityProcess | undefined = undefined; + private readonly useUtilityProcess = this.configurationService.getValue('window.experimental.sharedProcessUseUtilityProcess'); constructor( private readonly machineId: string, @@ -46,7 +54,8 @@ export class SharedProcess extends Disposable implements ISharedProcess { @ILoggerMainService private readonly loggerMainService: ILoggerMainService, @IPolicyService private readonly policyService: IPolicyService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IProtocolMainService private readonly protocolMainService: IProtocolMainService + @IProtocolMainService private readonly protocolMainService: IProtocolMainService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -79,7 +88,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { await this.whenReady(); - // connect to the shared process window + // connect to the shared process const port = await this.connect(); // Check back if the requesting window meanwhile closed @@ -101,7 +110,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { const disposables = new DisposableStore(); const disposeWorker = (reason: string) => { - if (!this.isAlive()) { + if (!this.isWindowAlive()) { return; // the shared process is already gone, no need to dispose anything } @@ -111,7 +120,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { disposables.dispose(); // Send this into the shared process who owns workers - this.send('vscode:electron-main->shared-process=disposeWorker', configuration); + this.sendToWindow('vscode:electron-main->shared-process=disposeWorker', configuration); }; // Ensure the sender is a valid target to send to @@ -135,40 +144,46 @@ export class SharedProcess extends Disposable implements ISharedProcess { private onWillShutdown(): void { this.logService.trace('SharedProcess: onWillShutdown'); - const window = this.window; - if (!window) { - return; // possibly too early before created - } + if (this.utilityProcess) { + this.utilityProcess.postMessage('vscode:electron-main->shared-process=exit'); - // Signal exit to shared process when shutting down - this.send('vscode:electron-main->shared-process=exit'); - - // Shut the shared process down when we are quitting - // - // Note: because we veto the window close, we must first remove our veto. - // Otherwise the application would never quit because the shared process - // window is refusing to close! - // - if (this.windowCloseListener) { - window.removeListener('close', this.windowCloseListener); - this.windowCloseListener = undefined; - } - - // Electron seems to crash on Windows without this setTimeout :| - setTimeout(() => { - try { - this.logService.trace('SharedProcess: onWillShutdown window.close()'); - window.close(); - } catch (error) { - this.logService.trace(`SharedProcess: onWillShutdown window.close() error: ${error}`); // ignore, as electron is already shutting down + this.utilityProcess = undefined; + } else { + const window = this.window; + if (!window) { + return; // possibly too early before created } - this.window = undefined; - }, 0); + // Signal exit to shared process when shutting down + this.sendToWindow('vscode:electron-main->shared-process=exit'); + + // Shut the shared process down when we are quitting + // + // Note: because we veto the window close, we must first remove our veto. + // Otherwise the application would never quit because the shared process + // window is refusing to close! + // + if (this.windowCloseListener) { + window.removeListener('close', this.windowCloseListener); + this.windowCloseListener = undefined; + } + + // Electron seems to crash on Windows without this setTimeout :| + setTimeout(() => { + try { + this.logService.trace('SharedProcess: onWillShutdown window.close()'); + window.close(); + } catch (error) { + this.logService.trace(`SharedProcess: onWillShutdown window.close() error: ${error}`); // ignore, as electron is already shutting down + } + + this.window = undefined; + }, 0); + } } - private send(channel: string, ...args: any[]): void { - if (!this.isAlive()) { + private sendToWindow(channel: string, ...args: any[]): void { + if (!this.isWindowAlive()) { this.logService.warn(`Sending IPC message to channel '${channel}' for shared process that is destroyed`); return; } @@ -183,13 +198,21 @@ export class SharedProcess extends Disposable implements ISharedProcess { private _whenReady: Promise | undefined = undefined; whenReady(): Promise { if (!this._whenReady) { - // Overall signal that the shared process window was loaded and - // all services within have been created. - this._whenReady = new Promise(resolve => validatedIpcMain.once('vscode:shared-process->electron-main=init-done', () => { - this.logService.trace('SharedProcess: Overall ready'); + this._whenReady = (async () => { - resolve(); - })); + // Overall signal that the shared process was loaded and + // all services within have been created. + + const whenReady = new DeferredPromise(); + if (this.utilityProcess) { + this.utilityProcess.once('vscode:shared-process->electron-main=init-done', () => whenReady.complete()); + } else { + validatedIpcMain.once('vscode:shared-process->electron-main=init-done', () => whenReady.complete()); + } + + await whenReady.p; + this.logService.info('SharedProcess: IPC ready'); + })(); } return this._whenReady; @@ -203,24 +226,64 @@ export class SharedProcess extends Disposable implements ISharedProcess { // Always wait for first window asking for connection await this.firstWindowConnectionBarrier.wait(); - // Create window for shared process - this.createWindow(); + // Spawn shared process + this.spawn(); - // Listeners - this.registerWindowListeners(); + // Wait for shared process indicating that IPC connections are accepted + const sharedProcessIpcReady = new DeferredPromise(); + if (this.utilityProcess) { + this.utilityProcess.once('vscode:shared-process->electron-main=ipc-ready', () => sharedProcessIpcReady.complete()); + } else { + validatedIpcMain.once('vscode:shared-process->electron-main=ipc-ready', () => sharedProcessIpcReady.complete()); + } - // Wait for window indicating that IPC connections are accepted - await new Promise(resolve => validatedIpcMain.once('vscode:shared-process->electron-main=ipc-ready', () => { - this.logService.trace('SharedProcess: IPC ready'); - - resolve(); - })); + await sharedProcessIpcReady.p; + this.logService.info('SharedProcess: IPC ready'); })(); } return this._whenIpcReady; } + private spawn(): void { + + // Spawn shared process + if (this.useUtilityProcess) { + this.createUtilityProcess(); + } else { + this.createWindow(); + } + + // Listeners + this.registerSharedProcessListeners(); + } + + private createUtilityProcess(): void { + this.utilityProcess = this._register(new UtilityProcess(this.logService, NullTelemetryService, this.lifecycleMainService)); + + this.utilityProcess.start({ + type: 'shared-process', + payload: this.createSharedProcessConfiguration(), + env: this.getEnv(), + execArgv: (!this.environmentMainService.isBuilt || this.environmentMainService.verbose) ? ['--nolazy', '--inspect=5896'] : undefined, // TODO@bpasero this make configurable + }); + } + + private getEnv(): NodeJS.ProcessEnv { + const env: NodeJS.ProcessEnv = { + ...deepClone(process.env), + VSCODE_AMD_ENTRYPOINT: 'vs/code/electron-browser/sharedProcess/sharedProcessMain', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: 'true', + VSCODE_PARENT_PID: String(process.pid) + }; + + // Sanitize environment + removeDangerousEnvVariables(env); + + return env; + } + private createWindow(): void { const configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); @@ -243,9 +306,16 @@ export class SharedProcess extends Disposable implements ISharedProcess { }); // Store into config object URL - configObjectUrl.update({ + configObjectUrl.update(this.createSharedProcessConfiguration()); + + // Load with config + this.window.loadURL(FileAccess.asBrowserUri(`vs/code/electron-browser/sharedProcess/sharedProcess${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); + } + + private createSharedProcessConfiguration(): ISharedProcessConfiguration { + return { machineId: this.machineId, - windowId: this.window.id, + windowId: this.window?.id ?? -1, // TODO@bpasero what window id should this be in utility process? appRoot: this.environmentMainService.appRoot, codeCachePath: this.environmentMainService.codeCachePath, profiles: this.userDataProfilesService.profiles, @@ -255,38 +325,39 @@ export class SharedProcess extends Disposable implements ISharedProcess { loggers: this.loggerMainService.getRegisteredLoggers(), product, policiesData: this.policyService.serialize() - }); - - // Load with config - this.window.loadURL(FileAccess.asBrowserUri(`vs/code/electron-browser/sharedProcess/sharedProcess${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); + }; } - private registerWindowListeners(): void { - if (!this.window) { - return; + private registerSharedProcessListeners(): void { + + // Hidden window + if (this.window) { + + // Prevent the window from closing + this.windowCloseListener = (e: ElectronEvent) => { + this.logService.trace('SharedProcess#close prevented'); + + // We never allow to close the shared process unless we get explicitly disposed() + e.preventDefault(); + + // Still hide the window though if visible + if (this.window?.isVisible()) { + this.window.hide(); + } + }; + + this.window.on('close', this.windowCloseListener); + + // Crashes & Unresponsive & Failed to load + this.window.webContents.on('render-process-gone', (event, details) => this._onDidError.fire({ type: WindowError.PROCESS_GONE, details })); + this.window.on('unresponsive', () => this._onDidError.fire({ type: WindowError.UNRESPONSIVE })); + this.window.webContents.on('did-fail-load', (event, exitCode, reason) => this._onDidError.fire({ type: WindowError.LOAD, details: { reason, exitCode } })); } - // Prevent the window from closing - this.windowCloseListener = (e: ElectronEvent) => { - this.logService.trace('SharedProcess#close prevented'); - - // We never allow to close the shared process unless we get explicitly disposed() - e.preventDefault(); - - // Still hide the window though if visible - if (this.window?.isVisible()) { - this.window.hide(); - } - }; - - this.window.on('close', this.windowCloseListener); - - // Crashes & Unresponsive & Failed to load - // We use `onUnexpectedError` explicitly because the error handler - // will send the error to the active window to log in devtools too - this.window.webContents.on('render-process-gone', (event, details) => this._onDidError.fire({ type: WindowError.PROCESS_GONE, details })); - this.window.on('unresponsive', () => this._onDidError.fire({ type: WindowError.UNRESPONSIVE })); - this.window.webContents.on('did-fail-load', (event, exitCode, reason) => this._onDidError.fire({ type: WindowError.LOAD, details: { reason, exitCode } })); + // Utility process + else if (this.utilityProcess) { + this._register(this.utilityProcess.onCrash(event => this._onDidError.fire({ type: WindowError.PROCESS_GONE, details: { reason: event.reason, exitCode: event.code } }))); + } } async connect(): Promise { @@ -295,8 +366,11 @@ export class SharedProcess extends Disposable implements ISharedProcess { await this.whenIpcReady; // Connect and return message port - const window = assertIsDefined(this.window); - return connectMessagePort(window); + if (this.utilityProcess) { + return this.utilityProcess.connect(); + } else { + return connectMessagePort(assertIsDefined(this.window)); + } } async toggle(): Promise { @@ -321,7 +395,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { return this.window?.isVisible() ?? false; } - private isAlive(): boolean { + private isWindowAlive(): boolean { const window = this.window; if (!window) { return false; diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 44e8d0c5984..b49e76fe71e 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, Details, app, MessageChannelMain } from 'electron'; +import { BrowserWindow, Details, app, MessageChannelMain, MessagePortMain } from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; @@ -304,6 +304,13 @@ export class UtilityProcess extends Disposable { this.process.postMessage(message, transfer); } + connect(payload?: unknown): MessagePortMain { + const { port1: outPort, port2: utilityProcessPort } = new MessageChannelMain(); + this.postMessage(payload, [utilityProcessPort]); + + return outPort; + } + enableInspectPort(): boolean { if (!this.process || typeof this.processPid !== 'number') { return false; @@ -389,11 +396,8 @@ export class WindowUtilityProcess extends UtilityProcess { // Register to window events this.registerWindowListeners(responseWindow, configuration); - // Establish message ports - const { port1: windowPort, port2: utilityProcessPort } = new MessageChannelMain(); - this.postMessage(configuration.payload, [utilityProcessPort]); - - // Exchange message ports to window + // Establish & exchange message ports + const windowPort = this.connect(configuration.payload); responseWindow.webContents.postMessage(configuration.responseChannel, configuration.responseNonce, [windowPort]); return true; From c8031c768515857baf1e116bcba0b562d3d0eca4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Feb 2023 09:19:56 +0100 Subject: [PATCH 52/85] sandbox - fix missing event treatment --- src/vs/platform/sharedProcess/electron-main/sharedProcess.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 03ff3f3835f..6353ea0b6cb 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -200,6 +200,9 @@ export class SharedProcess extends Disposable implements ISharedProcess { if (!this._whenReady) { this._whenReady = (async () => { + // Wait for shared process being ready to accept connection + await this.whenIpcReady; + // Overall signal that the shared process was loaded and // all services within have been created. From 5bb33d4b1bc7f19c3de5d2c3a392931a229a3427 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 16 Feb 2023 14:23:33 +0100 Subject: [PATCH 53/85] simplified the code --- .../stickyScroll/browser/stickyScroll.css | 35 ++++++++-------- .../browser/stickyScrollController.ts | 5 +-- .../browser/stickyScrollWidget.ts | 41 ++----------------- 3 files changed, 24 insertions(+), 57 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index 31b8293ec39..a303d5f4945 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -4,40 +4,41 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .sticky-line { - color : var(--vscode-editorLineNumber-foreground); - overflow : hidden; - white-space : nowrap; - display : inline-block; + color: var(--vscode-editorLineNumber-foreground); + overflow: hidden; + white-space: nowrap; + display: inline-block; } .monaco-editor .sticky-line-number { - text-align : right; - float : left; + text-align: right; + float: left; } .monaco-editor .sticky-line-root { - background-color : inherit; - overflow : hidden; - white-space : nowrap; - width : 100%; + background-color: inherit; + overflow: hidden; + white-space: nowrap; + width: 100%; } -.monaco-editor .sticky-widget.high-contrast{ +.monaco-editor.hc-black .sticky-widget, +.monaco-editor.hc-white .sticky-widget { border-bottom: 1px solid var(--vscode-contrastBorder); } .monaco-editor .sticky-line-root:hover { background-color: var(--vscode-editorStickyScrollHover-background); - cursor : pointer; + cursor: pointer; } .monaco-editor .sticky-widget { - width : 100%; - box-shadow : var(--vscode-scrollbar-shadow) 0 3px 2px -2px; - z-index : 11; - background-color : var(--vscode-editorStickyScroll-background); + width: 100%; + box-shadow: var(--vscode-scrollbar-shadow) 0 3px 2px -2px; + z-index: 11; + background-color: var(--vscode-editorStickyScroll-background); } .monaco-editor .sticky-widget.peek { - background-color : var(--vscode-peekViewEditorStickyScroll-background); + background-color: var(--vscode-peekViewEditorStickyScroll-background); } diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 83c79f45b5f..26ff4c2c6ce 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -15,7 +15,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; export class StickyScrollController extends Disposable implements IEditorContribution { @@ -33,10 +32,10 @@ export class StickyScrollController extends Disposable implements IEditorContrib @IContextMenuService private readonly _contextMenuService: IContextMenuService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @IInstantiationService instaService: IInstantiationService, - @IThemeService themeService: IThemeService ) { super(); - this._stickyScrollWidget = new StickyScrollWidget(this._editor, languageFeaturesService, instaService, themeService); + + this._stickyScrollWidget = new StickyScrollWidget(this._editor, languageFeaturesService, instaService); this._stickyLineCandidateProvider = new StickyLineCandidateProvider(this._editor, languageFeaturesService); this._widgetState = new StickyScrollWidgetState([], 0); diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index b4cb9ba217f..0daf442c877 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -20,14 +20,6 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import 'vs/css!./stickyScroll'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export enum ColorScheme { - DARK = 'dark', - LIGHT = 'light', - HIGH_CONTRAST_LIGHT = 'hcLight', - HIGH_CONTRAST_DARK = 'hcDark' -} interface CustomMouseEvent { detail: string; @@ -55,14 +47,11 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { private _hoverOnColumn: number = -1; private _stickyRangeProjectedOnEditor: IRange | undefined; private _candidateDefinitionsLength: number = -1; - private _colorSchemeType: ColorScheme | undefined; - private _widgetHeight: number = 0; constructor( private readonly _editor: ICodeEditor, @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, - @IInstantiationService private readonly _instaService: IInstantiationService, - @IThemeService private readonly _themeService: IThemeService + @IInstantiationService private readonly _instaService: IInstantiationService ) { super(); this._layoutInfo = this._editor.getLayoutInfo(); @@ -70,20 +59,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._rootDomNode.className = 'sticky-widget'; this._rootDomNode.classList.toggle('peek', _editor instanceof EmbeddedCodeEditorWidget); this._rootDomNode.style.width = `${this._layoutInfo.width - this._layoutInfo.minimap.minimapCanvasOuterWidth - this._layoutInfo.verticalScrollbarWidth}px`; - this._colorSchemeType = this._themeService.getColorTheme().type; - // Listener on the color theme is used in order to keep track of whether a border on the sticky widget is needed - this._themeService.onDidColorThemeChange((colorTheme) => { - this._colorSchemeType = colorTheme.type; - // classList.toggle() is not used because if we move from HIGH_CONTRAST_DARK to HIGH_CONTRAST_LIGHT we want to keep the class name 'high-contrast' - if ((this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK - || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) - && this._widgetHeight > 0) { - this._rootDomNode.classList.add('high-contrast'); - } else { - this._rootDomNode.classList.remove('high-contrast'); - } - }); this._register(this._updateLinkGesture()); } @@ -306,18 +282,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._rootDomNode.appendChild(this._renderChildNode(index, line)); } const editorLineHeight = this._editor.getOption(EditorOption.lineHeight); - this._widgetHeight = this._lineNumbers.length * editorLineHeight + this._lastLineRelativePosition; - this._rootDomNode.style.height = this._widgetHeight.toString() + 'px'; - if (this._colorSchemeType === ColorScheme.HIGH_CONTRAST_DARK || this._colorSchemeType === ColorScheme.HIGH_CONTRAST_LIGHT) { - // When the widget height is zero remove the bottom border, else there will be a double border below the breadcrumbs bar - if (this._widgetHeight === 0) { - this._rootDomNode.classList.remove('high-contrast'); - } - // Otherwise if the widget height is bigger than 0 and previously the class name 'high-contrast' was removed, set it again - else if (!this._rootDomNode.classList.contains('high-contrast')) { - this._rootDomNode.classList.add('high-contrast'); - } - } + const widgetHeight: number = this._lineNumbers.length * editorLineHeight + this._lastLineRelativePosition; + this._rootDomNode.style.display = widgetHeight > 0 ? 'block' : 'none'; + this._rootDomNode.style.height = widgetHeight.toString() + 'px'; const minimapSide = this._editor.getOption(EditorOption.minimap).side; if (minimapSide === 'left') { this._rootDomNode.style.marginLeft = this._editor.getLayoutInfo().minimap.minimapCanvasOuterWidth + 'px'; From d0b8f8a6551fe2fc39456d213d3dab535440f3ca Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 16 Feb 2023 15:01:53 +0100 Subject: [PATCH 54/85] Add doc info to comments context keys (#174576) Fixes https://github.com/microsoft/vscode-docs/issues/6052 --- .../contrib/comments/browser/commentNode.ts | 6 +++++- .../comments/browser/commentThreadWidget.ts | 6 +++--- .../comments/common/commentContextKeys.ts | 20 ++++++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 87a32fe2123..f8414f2b54e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -44,6 +44,7 @@ import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { DomEmitter } from 'vs/base/browser/event'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export class CommentNode extends Disposable { private _domNode: HTMLElement; @@ -104,7 +105,10 @@ export class CommentNode extends Disposable { this._domNode = dom.$('div.review-comment'); this._contextKeyService = contextKeyService.createScoped(this._domNode); - this._commentContextValue = this._contextKeyService.createKey('comment', comment.contextValue); + this._commentContextValue = CommentContextKeys.commentContext.bindTo(this._contextKeyService); + if (this.comment.contextValue) { + this._commentContextValue.set(this.comment.contextValue); + } this._commentMenus = this.commentService.getCommentMenus(this.owner); this._domNode.tabIndex = -1; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 370a60796b9..7c9a90a09b5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -105,13 +105,13 @@ export class CommentThreadWidget extends this._styleElement = dom.createStyleSheet(this.container); - this._commentThreadContextValue = this._contextKeyService.createKey('commentThread', undefined); + this._commentThreadContextValue = CommentContextKeys.commentThreadContext.bindTo(this._contextKeyService); this._commentThreadContextValue.set(_commentThread.contextValue); - const commentControllerKey = this._contextKeyService.createKey('commentController', undefined); + const commentControllerKey = CommentContextKeys.commentControllerContext.bindTo(this._contextKeyService); const controller = this.commentService.getCommentController(this._owner); - if (controller) { + if (controller?.contextValue) { commentControllerKey.set(controller.contextValue); } diff --git a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts index 3593481d5ca..c4b32556b31 100644 --- a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts +++ b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts @@ -3,15 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + export namespace CommentContextKeys { /** * A context key that is set when the comment thread has no comments. */ - export const commentThreadIsEmpty = new RawContextKey('commentThreadIsEmpty', false); + export const commentThreadIsEmpty = new RawContextKey('commentThreadIsEmpty', false, { type: 'boolean', description: nls.localize('commentThreadIsEmpty', "Set when the comment thread has no comments") }); /** * A context key that is set when the comment has no input. */ - export const commentIsEmpty = new RawContextKey('commentIsEmpty', false); -} \ No newline at end of file + export const commentIsEmpty = new RawContextKey('commentIsEmpty', false, { type: 'boolean', description: nls.localize('commentIsEmpty', "Set when the comment has no input") }); + /** + * The context value of the comment. + */ + export const commentContext = new RawContextKey('comment', undefined, { type: 'string', description: nls.localize('comment', "The context value of the comment") }); + /** + * The context value of the comment thread. + */ + export const commentThreadContext = new RawContextKey('commentThread', undefined, { type: 'string', description: nls.localize('commentThread', "The context value of the comment thread") }); + /** + * The comment controller id associated with a comment thread. + */ + export const commentControllerContext = new RawContextKey('commentController', undefined, { type: 'string', description: nls.localize('commentController', "The comment controller id associated with a comment thread") }); +} From 7b7f2c033d8e58baae060c1d6bb943fc9650dfda Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 16 Feb 2023 15:06:34 +0100 Subject: [PATCH 55/85] Add drop down to quick diff (#174353) * Add dropdown to quick diff when multiple providers Fixes #169012 * Fix git revert action in quick diff Fixes #172432 * Make dropdown drive entire quick diff peek - "x of y" detail - action bar * Handle providers being removed * Delete unused css * Address PR feedback --- .../browser/ui/actionbar/actionViewItems.ts | 4 +- .../contrib/peekView/browser/peekView.ts | 13 +- .../debug/browser/debugActionViewItems.ts | 4 +- .../remote/browser/explorerViewItems.ts | 4 +- .../contrib/scm/browser/dirtyDiffSwitcher.ts | 64 +++++ .../contrib/scm/browser/dirtydiffDecorator.ts | 229 +++++++++++++----- .../scm/browser/media/dirtydiffDecorator.css | 15 ++ 7 files changed, 256 insertions(+), 77 deletions(-) create mode 100644 src/vs/workbench/contrib/scm/browser/dirtyDiffSwitcher.ts diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 35d21fcc416..a6e7f77a374 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -415,7 +415,7 @@ export class ActionViewItem extends BaseActionViewItem { } } -export class SelectActionViewItem extends BaseActionViewItem { +export class SelectActionViewItem extends BaseActionViewItem { protected selectBox: SelectBox; constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) { @@ -444,7 +444,7 @@ export class SelectActionViewItem extends BaseActionViewItem { this.actionRunner.run(this._action, this.getActionContext(option, index)); } - protected getActionContext(option: string, index: number) { + protected getActionContext(option: string, index: number): T | string { return option; } diff --git a/src/vs/editor/contrib/peekView/browser/peekView.ts b/src/vs/editor/contrib/peekView/browser/peekView.ts index 08427a42eca..49df433bf1a 100644 --- a/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -112,6 +112,7 @@ export abstract class PeekViewWidget extends ZoneWidget { private disposed?: true; protected _headElement?: HTMLDivElement; + protected _titleElement?: HTMLDivElement; protected _primaryHeading?: HTMLElement; protected _secondaryHeading?: HTMLElement; protected _metaHeading?: HTMLElement; @@ -180,18 +181,18 @@ export abstract class PeekViewWidget extends ZoneWidget { } protected _fillHead(container: HTMLElement, noCloseAction?: boolean): void { - const titleElement = dom.$('.peekview-title'); + this._titleElement = dom.$('.peekview-title'); if ((this.options as IPeekViewOptions).supportOnTitleClick) { - titleElement.classList.add('clickable'); - dom.addStandardDisposableListener(titleElement, 'click', event => this._onTitleClick(event)); + this._titleElement.classList.add('clickable'); + dom.addStandardDisposableListener(this._titleElement, 'click', event => this._onTitleClick(event)); } - dom.append(this._headElement!, titleElement); + dom.append(this._headElement!, this._titleElement); - this._fillTitleIcon(titleElement); + this._fillTitleIcon(this._titleElement); this._primaryHeading = dom.$('span.filename'); this._secondaryHeading = dom.$('span.dirname'); this._metaHeading = dom.$('span.meta'); - dom.append(titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading); + dom.append(this._titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading); const actionsContainer = dom.$('.peekview-actions'); dom.append(this._headElement!, actionsContainer); diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index b5e9bff1a64..8eae93927bf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -253,7 +253,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { } } -export class FocusSessionActionViewItem extends SelectActionViewItem { +export class FocusSessionActionViewItem extends SelectActionViewItem { constructor( action: IAction, session: IDebugSession | undefined, @@ -286,7 +286,7 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { this.update(selectedSession); } - protected override getActionContext(_: string, index: number): any { + protected override getActionContext(_: string, index: number): IDebugSession { return this.getSessions()[index]; } diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index f64ec7f1aab..a1da7375df7 100644 --- a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -26,7 +26,7 @@ interface IRemoteSelectItem extends ISelectOptionItem { virtualWorkspace?: string; } -export class SwitchRemoteViewItem extends SelectActionViewItem { +export class SwitchRemoteViewItem extends SelectActionViewItem { constructor( action: IAction, @@ -92,7 +92,7 @@ export class SwitchRemoteViewItem extends SelectActionViewItem { } } - protected override getActionContext(_: string, index: number): any { + protected override getActionContext(_: string, index: number): IRemoteSelectItem { return this.optionsItems[index]; } diff --git a/src/vs/workbench/contrib/scm/browser/dirtyDiffSwitcher.ts b/src/vs/workbench/contrib/scm/browser/dirtyDiffSwitcher.ts new file mode 100644 index 00000000000..b2db48e1881 --- /dev/null +++ b/src/vs/workbench/contrib/scm/browser/dirtyDiffSwitcher.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action, IAction } from 'vs/base/common/actions'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { peekViewTitleBackground } from 'vs/editor/contrib/peekView/browser/peekView'; +import { Color } from 'vs/base/common/color'; + +export interface IQuickDiffSelectItem extends ISelectOptionItem { + provider: string; +} + +export class SwitchQuickDiffViewItem extends SelectActionViewItem { + private readonly optionsItems: IQuickDiffSelectItem[]; + + constructor( + action: IAction, + providers: string[], + selected: string, + @IContextViewService contextViewService: IContextViewService, + @IThemeService themeService: IThemeService + ) { + const items = providers.map(provider => ({ provider, text: provider })); + let startingSelection = providers.indexOf(selected); + if (startingSelection === -1) { + startingSelection = 0; + } + const styles = { ...defaultSelectBoxStyles }; + const theme = themeService.getColorTheme(); + styles.selectBackground = theme.getColor(peekViewTitleBackground)?.lighten(.7).toString() || Color.transparent.toString(); + super(null, action, items, startingSelection, contextViewService, styles, { ariaLabel: nls.localize('remotes', 'Switch quick diff base') }); + this.optionsItems = items; + } + + public setSelection(provider: string) { + const index = this.optionsItems.findIndex(item => item.provider === provider); + this.select(index); + } + + protected override getActionContext(_: string, index: number): IQuickDiffSelectItem { + return this.optionsItems[index]; + } +} + +export class SwitchQuickDiffBaseAction extends Action { + + public static readonly ID = 'quickDiff.base.switch'; + public static readonly LABEL = nls.localize('quickDiff.base.switch', "Switch Quick Diff Base"); + + constructor(private readonly callback: (event?: IQuickDiffSelectItem) => void) { + super(SwitchQuickDiffBaseAction.ID, SwitchQuickDiffBaseAction.LABEL, undefined, undefined); + } + + override async run(event?: IQuickDiffSelectItem): Promise { + return this.callback(event); + } +} diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 60cc0583abc..a2dbbbe0fa2 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -42,7 +42,7 @@ import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition import { equals, sortedDiff } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ISplice } from 'vs/base/common/sequence'; -import { createStyleSheet } from 'vs/base/browser/dom'; +import * as dom from 'vs/base/browser/dom'; import { EncodingMode, ITextFileEditorModel, IResolvedTextFileEditorModel, ITextFileService, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { gotoNextLocation, gotoPreviousLocation } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; @@ -59,6 +59,7 @@ import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickDiffService, QuickDiff } from 'vs/workbench/contrib/scm/common/quickDiff'; +import { IQuickDiffSelectItem, SwitchQuickDiffBaseAction, SwitchQuickDiffViewItem } from 'vs/workbench/contrib/scm/browser/dirtyDiffSwitcher'; class DiffActionRunner extends ActionRunner { @@ -180,9 +181,12 @@ class DirtyDiffWidget extends PeekViewWidget { private diffEditor!: EmbeddedDiffEditorWidget; private title: string; private menu: IMenu; - private index: number = 0; + private _index: number = 0; + private _provider: string = ''; private change: IChange | undefined; private height: number | undefined = undefined; + private dropdown: SwitchQuickDiffViewItem | undefined; + private dropdownContainer: HTMLElement | undefined; constructor( editor: ICodeEditor, @@ -192,7 +196,7 @@ class DirtyDiffWidget extends PeekViewWidget { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(editor, { isResizeable: true, frameWidth: 1, keepEditorSelection: true }, instantiationService); + super(editor, { isResizeable: true, frameWidth: 1, keepEditorSelection: true, className: 'dirty-diff' }, instantiationService); this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this)); this._applyTheme(themeService.getColorTheme()); @@ -213,10 +217,25 @@ class DirtyDiffWidget extends PeekViewWidget { this.setTitle(this.title); } - showChange(index: number): void { + get provider(): string { + return this._provider; + } + + get index(): number { + return this._index; + } + + get visibleRange(): Range | undefined { + const visibleRanges = this.diffEditor.getModifiedEditor().getVisibleRanges(); + return visibleRanges.length >= 0 ? visibleRanges[0] : undefined; + } + + showChange(index: number, usePosition: boolean = true): void { const labeledChange = this.model.changes[index]; const change = labeledChange.change; - this.index = index; + this._index = index; + this._provider = labeledChange.label; + const previousVisibleRange = this.visibleRange; this.change = change; const originalModel = this.model.original; @@ -229,13 +248,14 @@ class DirtyDiffWidget extends PeekViewWidget { // TODO@joao TODO@alex need this setTimeout probably because the // non-side-by-side diff still hasn't created the view zones - onFirstDiffUpdate(() => setTimeout(() => this.revealChange(change), 0)); + onFirstDiffUpdate(() => setTimeout(() => (usePosition || !previousVisibleRange) ? this.revealChange(change) : this.revealLines(previousVisibleRange), 0)); const diffEditorModel = this.model.getDiffEditorModel(labeledChange.uri.toString()); if (!diffEditorModel) { return; } this.diffEditor.setModel(diffEditorModel); + this.dropdown?.setSelection(labeledChange.label); const position = new Position(getModifiedEndLineNumber(change), 1); @@ -244,28 +264,91 @@ class DirtyDiffWidget extends PeekViewWidget { const editorHeightInLines = Math.floor(editorHeight / lineHeight); const height = Math.min(getChangeHeight(change) + /* padding */ 8, Math.floor(editorHeightInLines / 3)); - this.renderTitle(labeledChange.labels); + this.renderTitle(labeledChange.label); const changeType = getChangeType(change); const changeTypeColor = getChangeTypeColor(this.themeService.getColorTheme(), changeType); this.style({ frameColor: changeTypeColor, arrowColor: changeTypeColor }); - this._actionbarWidget!.context = [diffEditorModel.modified.uri, this.model.changes.map(change => change.change), index]; - this.show(position, height); + const providerSpecificChanges: IChange[] = []; + for (const change of this.model.changes) { + if (change.label === this.model.changes[this._index].label) { + providerSpecificChanges.push(change.change); + } + } + this._actionbarWidget!.context = [diffEditorModel.modified.uri, providerSpecificChanges, index]; + if (usePosition) { + this.show(position, height); + } this.editor.focus(); } - private renderTitle(labels: string[]): void { - const detail = this.model.changes.length > 1 - ? nls.localize('changes', "{0} - {1} of {2} changes", labels.join(', '), this.index + 1, this.model.changes.length) - : nls.localize('change', "{0} - {1} of {2} change", labels.join(', '), this.index + 1, this.model.changes.length); + private renderTitle(label: string): void { + const providerChanges = this.model.mapChanges.get(label)!; + const providerIndex = providerChanges.indexOf(this._index); + + let detail: string; + if (!this.shouldUseDropdown()) { + detail = this.model.changes.length > 1 + ? nls.localize('changes', "{0} - {1} of {2} changes", label, providerIndex + 1, providerChanges.length) + : nls.localize('change', "{0} - {1} of {2} change", label, providerIndex + 1, providerChanges.length); + this.dropdownContainer!.style.display = 'none'; + } else { + detail = this.model.changes.length > 1 + ? nls.localize('multiChanges', "{0} of {1} changes", providerIndex + 1, providerChanges.length) + : nls.localize('multiChange', "{0} of {1} change", providerIndex + 1, providerChanges.length); + this.dropdownContainer!.style.display = 'inherit'; + } this.setTitle(this.title, detail); } + private switchQuickDiff(event?: IQuickDiffSelectItem) { + const newProvider = event?.provider; + if (newProvider === this.model.changes[this._index].label) { + return; + } + let closestGreaterIndex = this._index < this.model.changes.length - 1 ? this._index + 1 : 0; + for (let i = closestGreaterIndex; i !== this._index; i < this.model.changes.length - 1 ? i++ : i = 0) { + if (this.model.changes[i].label === newProvider) { + closestGreaterIndex = i; + break; + } + } + let closestLesserIndex = this._index > 0 ? this._index - 1 : this.model.changes.length - 1; + for (let i = closestLesserIndex; i !== this._index; i >= 0 ? i-- : i = this.model.changes.length - 1) { + if (this.model.changes[i].label === newProvider) { + closestLesserIndex = i; + break; + } + } + const closestIndex = Math.abs(this.model.changes[closestGreaterIndex].change.modifiedEndLineNumber - this.model.changes[this._index].change.modifiedEndLineNumber) + < Math.abs(this.model.changes[closestLesserIndex].change.modifiedEndLineNumber - this.model.changes[this._index].change.modifiedEndLineNumber) + ? closestGreaterIndex : closestLesserIndex; + this.showChange(closestIndex, false); + } + + private shouldUseDropdown(): boolean { + let providersWithChangesCount = 0; + if (this.model.mapChanges.size > 1) { + const keys = Array.from(this.model.mapChanges.keys()); + for (let i = 0; (i < keys.length) && (providersWithChangesCount <= 1); i++) { + if (this.model.mapChanges.get(keys[i])!.length > 0) { + providersWithChangesCount++; + } + } + } + return providersWithChangesCount >= 2; + } + protected override _fillHead(container: HTMLElement): void { super._fillHead(container, true); + this.dropdownContainer = dom.prepend(this._titleElement!, dom.$('.dropdown')); + this.dropdown = this.instantiationService.createInstance(SwitchQuickDiffViewItem, new SwitchQuickDiffBaseAction((event?: IQuickDiffSelectItem) => this.switchQuickDiff(event)), + this.model.quickDiffs.map(quickDiffer => quickDiffer.label), this.model.changes[this._index].label); + this.dropdown.render(this.dropdownContainer); + const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(this.editor), ThemeIcon.asClassName(gotoPreviousLocation)); const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(this.editor), ThemeIcon.asClassName(gotoNextLocation)); @@ -354,6 +437,10 @@ class DirtyDiffWidget extends PeekViewWidget { this.diffEditor.revealLinesInCenter(start, end, ScrollType.Immediate); } + private revealLines(range: Range): void { + this.diffEditor.revealLinesInCenter(range.startLineNumber, range.endLineNumber, ScrollType.Immediate); + } + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekViewBorder) || Color.transparent; this.style({ @@ -606,7 +693,6 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu private model: DirtyDiffModel | null = null; private widget: DirtyDiffWidget | null = null; - private currentIndex: number = -1; private readonly isDirtyDiffVisible!: IContextKey; private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; @@ -622,7 +708,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu ) { super(); this.enabled = !contextKeyService.getContextKeyValue('isInDiffEditor'); - this.stylesheet = createStyleSheet(); + this.stylesheet = dom.createStyleSheet(); this._register(toDisposable(() => this.stylesheet.remove())); if (this.enabled) { @@ -667,7 +753,11 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu } canNavigate(): boolean { - return this.currentIndex === -1 || (!!this.model && this.model.changes.length > 1); + return this.widget?.index === -1 || (!!this.model && this.model.changes.length > 1); + } + + refresh(): void { + this.widget?.showChange(this.widget.index, false); } next(lineNumber?: number): void { @@ -678,13 +768,16 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return; } - if (this.editor.hasModel() && (typeof lineNumber === 'number' || this.currentIndex === -1)) { - this.currentIndex = this.model.findNextClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber); + let index: number; + if (this.editor.hasModel() && (typeof lineNumber === 'number')) { + index = this.model.findNextClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber, true, this.widget.provider); } else { - this.currentIndex = rot(this.currentIndex + 1, this.model.changes.length); + const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value; + const mapIndex = providerChanges.findIndex(value => value === this.widget!.index); + index = providerChanges[rot(mapIndex + 1, providerChanges.length)]; } - this.widget.showChange(this.currentIndex); + this.widget.showChange(index); } previous(lineNumber?: number): void { @@ -695,13 +788,16 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return; } - if (this.editor.hasModel() && (typeof lineNumber === 'number' || this.currentIndex === -1)) { - this.currentIndex = this.model.findPreviousClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber); + let index: number; + if (this.editor.hasModel() && (typeof lineNumber === 'number')) { + index = this.model.findPreviousClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber, true, this.widget.provider); } else { - this.currentIndex = rot(this.currentIndex - 1, this.model.changes.length); + const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value; + const mapIndex = providerChanges.findIndex(value => value === this.widget!.index); + index = providerChanges[rot(mapIndex - 1, providerChanges.length)]; } - this.widget.showChange(this.currentIndex); + this.widget.showChange(index); } close(): void { @@ -743,7 +839,6 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return false; } - this.currentIndex = -1; this.model = model; this.widget = this.instantiationService.createInstance(DirtyDiffWidget, this.editor, model); this.isDirtyDiffVisible.set(true); @@ -759,7 +854,6 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu disposables.add(toDisposable(() => { this.model = null; this.widget = null; - this.currentIndex = -1; this.isDirtyDiffVisible.set(false); this.editor.focus(); })); @@ -774,16 +868,13 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu } for (const splice of splices) { - if (splice.start <= this.currentIndex) { - if (this.currentIndex < splice.start + splice.deleteCount) { - this.currentIndex = -1; - this.next(); - } else { - this.currentIndex = rot(this.currentIndex + splice.toInsert.length - splice.deleteCount - 1, this.model.changes.length); - this.next(); - } + if (splice.start <= this.widget.index) { + this.next(); + return; } } + + this.refresh(); } private onEditorMouseDown(e: IEditorMouseEvent): void { @@ -861,7 +952,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return; } - if (index === this.currentIndex) { + if (index === this.widget?.index) { this.close(); } else { this.next(lineNumber); @@ -1110,7 +1201,7 @@ export async function getOriginalResource(quickDiffService: IQuickDiffService, u return quickDiffs.length > 0 ? quickDiffs[0].originalResource : null; } -type LabeledChange = { change: IChange; labels: string[]; uri: URI }; +type LabeledChange = { change: IChange; label: string; uri: URI }; export class DirtyDiffModel extends Disposable { @@ -1120,7 +1211,7 @@ export class DirtyDiffModel extends Disposable { private _model: ITextFileEditorModel; get original(): ITextModel[] { return this._originalTextModels; } - private diffDelayer = new ThrottledDelayer(200); + private diffDelayer = new ThrottledDelayer<{ changes: LabeledChange[]; mapChanges: Map } | null>(200); private _quickDiffsPromise?: Promise; private repositoryDisposables = new Set(); private readonly originalModelDisposables = this._register(new DisposableStore()); @@ -1131,6 +1222,8 @@ export class DirtyDiffModel extends Disposable { private _changes: LabeledChange[] = []; get changes(): LabeledChange[] { return this._changes; } + private _mapChanges: Map = new Map(); // key is the quick diff name, value is the index of the change in this._changes + get mapChanges(): Map { return this._mapChanges; } constructor( textFileModel: IResolvedTextFileEditorModel, @@ -1161,13 +1254,17 @@ export class DirtyDiffModel extends Disposable { this._originalModels.clear(); this._originalTextModels = []; this._quickDiffsPromise = undefined; - this.setChanges([]); + this.setChanges([], new Map()); this.triggerDiff(); })); this.triggerDiff(); } + get quickDiffs(): readonly QuickDiff[] { + return this._quickDiffs; + } + public getDiffEditorModel(originalUri: string): IDiffEditorModel | undefined { if (!this._originalModels.has(originalUri)) { return; @@ -1202,40 +1299,41 @@ export class DirtyDiffModel extends Disposable { return this.diffDelayer .trigger(() => this.diff()) - .then((changes: LabeledChange[] | null) => { + .then((result: { changes: LabeledChange[]; mapChanges: Map } | null) => { const originalModels = Array.from(this._originalModels.values()); - if (this._disposed || this._model.isDisposed() || originalModels.some(originalModel => originalModel.isDisposed())) { + if (!result || this._disposed || this._model.isDisposed() || originalModels.some(originalModel => originalModel.isDisposed())) { return; // disposed } if (originalModels.every(originalModel => originalModel.textEditorModel.getValueLength() === 0)) { - changes = []; + result.changes = []; } - if (!changes) { - changes = []; + if (!result.changes) { + result.changes = []; } - this.setChanges(changes); + this.setChanges(result.changes, result.mapChanges); }, (err) => onUnexpectedError(err)); } - private setChanges(changes: LabeledChange[]): void { + private setChanges(changes: LabeledChange[], mapChanges: Map): void { const diff = sortedDiff(this._changes, changes, (a, b) => compareChanges(a.change, b.change)); this._changes = changes; + this._mapChanges = mapChanges; this._onDidChange.fire({ changes, diff }); } - private diff(): Promise { + private diff(): Promise<{ changes: LabeledChange[]; mapChanges: Map } | null> { return this.progressService.withProgress({ location: ProgressLocation.Scm, delay: 250 }, async () => { const originalURIs = await this.getQuickDiffsPromise(); if (this._disposed || this._model.isDisposed() || (originalURIs.length === 0)) { - return Promise.resolve([]); // disposed + return Promise.resolve({ changes: [], mapChanges: new Map() }); // disposed } const filteredToDiffable = originalURIs.filter(quickDiff => this.editorWorkerService.canComputeDirtyDiff(quickDiff.originalResource, this._model.resource)); if (filteredToDiffable.length === 0) { - return Promise.resolve([]); // All files are too large + return Promise.resolve({ changes: [], mapChanges: new Map() }); // All files are too large } const ignoreTrimWhitespaceSetting = this.configurationService.getValue<'true' | 'false' | 'inherit'>('scm.diffDecorationsIgnoreTrimWhitespace'); @@ -1243,32 +1341,27 @@ export class DirtyDiffModel extends Disposable { ? this.configurationService.getValue('diffEditor.ignoreTrimWhitespace') : ignoreTrimWhitespaceSetting !== 'false'; - const allDiffs: { change: IChange; labels: string[]; uri: URI }[] = []; + const allDiffs: LabeledChange[] = []; for (const quickDiff of filteredToDiffable) { const dirtyDiff = await this.editorWorkerService.computeDirtyDiff(quickDiff.originalResource, this._model.resource, ignoreTrimWhitespace); if (dirtyDiff) { for (const diff of dirtyDiff) { if (diff) { - allDiffs.push({ change: diff, labels: [quickDiff.label], uri: quickDiff.originalResource }); + allDiffs.push({ change: diff, label: quickDiff.label, uri: quickDiff.originalResource }); } } } } const sorted = allDiffs.sort((a, b) => compareChanges(a.change, b.change)); - const reduced = []; - for (const diff of sorted) { - if (reduced.length === 0) { - reduced.push(diff); - } else { - const comparedChanges = compareChanges(reduced[reduced.length - 1].change, diff.change); - if (comparedChanges === 0) { - reduced[reduced.length - 1].labels.push(...diff.labels); - } else { - reduced.push(diff); - } + const map: Map = new Map(); + for (let i = 0; i < sorted.length; i++) { + const label = sorted[i].label; + if (!map.has(label)) { + map.set(label, []); } + map.get(label)!.push(i); } - return reduced; + return { changes: sorted, mapChanges: map }; }); } @@ -1340,8 +1433,11 @@ export class DirtyDiffModel extends Disposable { return this.quickDiffService.getQuickDiffs(uri, this._model.getLanguageId(), this._model.textEditorModel ? shouldSynchronizeModel(this._model.textEditorModel) : undefined); } - findNextClosestChange(lineNumber: number, inclusive = true): number { + findNextClosestChange(lineNumber: number, inclusive = true, provider?: string): number { for (let i = 0; i < this.changes.length; i++) { + if (provider && this.changes[i].label !== provider) { + continue; + } const change = this.changes[i].change; if (inclusive) { @@ -1358,8 +1454,11 @@ export class DirtyDiffModel extends Disposable { return 0; } - findPreviousClosestChange(lineNumber: number, inclusive = true): number { + findPreviousClosestChange(lineNumber: number, inclusive = true, provider?: string): number { for (let i = this.changes.length - 1; i >= 0; i--) { + if (provider && this.changes[i].label !== provider) { + continue; + } const change = this.changes[i].change; if (inclusive) { @@ -1422,7 +1521,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor @ITextFileService private readonly textFileService: ITextFileService ) { super(); - this.stylesheet = createStyleSheet(); + this.stylesheet = dom.createStyleSheet(); this._register(toDisposable(() => this.stylesheet.parentElement!.removeChild(this.stylesheet))); const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorations')); diff --git a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css index de528c90683..fe5686a3eee 100644 --- a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css +++ b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css @@ -93,3 +93,18 @@ bottom: 0; transition: height 80ms linear; } + +.dirty-diff .peekview-title .dropdown { + margin-right: 10px; +} + +.dirty-diff .peekview-title .dropdown.select-container { + cursor: default; +} + +.dirty-diff .peekview-title .dropdown .monaco-select-box { + cursor: pointer; + min-width: 100px; + min-height: 18px; + padding: 0px 23px 0px 8px; +} From 8fd698a555dc12dcd2b9fd819b2e86473bf3864d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Feb 2023 15:17:15 +0100 Subject: [PATCH 56/85] sandbox - utility process cleanup (#174564) * logging * log utility process * align envs --- src/vs/code/electron-main/app.ts | 3 + src/vs/platform/environment/common/argv.ts | 2 + .../environment/common/environmentService.ts | 14 ++--- src/vs/platform/environment/node/argv.ts | 2 + .../environment/node/environmentService.ts | 11 +++- .../test/node/environmentService.test.ts | 6 +- .../electron-main/extensionHostStarter.ts | 1 + .../electron-main/sharedProcess.ts | 60 ++++++++++--------- .../platform/terminal/node/ptyHostService.ts | 4 +- .../electron-main/utilityProcess.ts | 44 +++++++++++--- .../utilityProcessWorkerMainService.ts | 27 +++------ .../electron-main/windowsMainService.ts | 3 +- .../actions/developerActions.ts | 3 - 13 files changed, 104 insertions(+), 76 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 8bc4ff7f891..23c44b398cc 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -1263,6 +1263,7 @@ export class CodeApplication extends Disposable { type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The type of shared process crash to understand the nature of the crash better.' }; reason: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reason of the shared process crash to understand the nature of the crash better.' }; code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The exit code of the shared process crash to understand the nature of the crash better.' }; + utilityprocess: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If the shared process is using utility process or a hidden window.' }; visible: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the shared process window was visible or not.' }; shuttingdown: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the application is shutting down when the crash happens.' }; owner: 'bpasero'; @@ -1274,6 +1275,7 @@ export class CodeApplication extends Disposable { reason: string | undefined; code: number | undefined; visible: boolean; + utilityprocess: string; shuttingdown: boolean; }; telemetryService.publicLog2('sharedprocesserror', { @@ -1281,6 +1283,7 @@ export class CodeApplication extends Disposable { reason: details?.reason, code: details?.exitCode, visible: sharedProcess.isVisible(), + utilityprocess: sharedProcess.usingUtilityProcess() ? '1' : '0', // TODO@bpasero remove this once sandbox is enabled by default shuttingdown: willShutdown }); })); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index a932ea6ec5d..ed567c6ca6f 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -67,6 +67,8 @@ export interface NativeParsedArgs { 'inspect-brk-search'?: string; 'inspect-ptyhost'?: string; 'inspect-brk-ptyhost'?: string; + 'inspect-sharedprocess'?: string; + 'inspect-brk-sharedprocess'?: string; 'disable-extensions'?: boolean; 'disable-extension'?: string[]; // undefined or array of 1 or more 'list-extensions'?: boolean; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 0bd3493cbd5..b724b7a6a61 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -11,7 +11,7 @@ import { env } from 'vs/base/common/process'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { ExtensionKind, IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ExtensionKind, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductService } from 'vs/platform/product/common/productService'; export const EXTENSION_IDENTIFIER_WITH_LOG_REGEX = /^([^.]+\..+):(.+)$/; @@ -213,7 +213,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron } @memoize - get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this.args, this.isBuilt); } + get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostDebugPort(this.args, this.isBuilt); } get debugRenderer(): boolean { return !!this.args.debugRenderer; } get isBuilt(): boolean { return !env['VSCODE_DEV']; } @@ -278,17 +278,13 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron ) { } } -export function parseExtensionHostPort(args: NativeParsedArgs, isBuild: boolean): IExtensionHostDebugParams { +export function parseExtensionHostDebugPort(args: NativeParsedArgs, isBuild: boolean): IExtensionHostDebugParams { return parseDebugParams(args['inspect-extensions'], args['inspect-brk-extensions'], 5870, isBuild, args.debugId, args.extensionEnvironment); } -export function parsePtyHostPort(args: NativeParsedArgs, isBuild: boolean): IDebugParams { - return parseDebugParams(args['inspect-ptyhost'], args['inspect-brk-ptyhost'], 5877, isBuild, args.extensionEnvironment); -} - -function parseDebugParams(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuild: boolean, debugId?: string, environmentString?: string): IExtensionHostDebugParams { +export function parseDebugParams(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuilt: boolean, debugId?: string, environmentString?: string): IExtensionHostDebugParams { const portStr = debugBrkArg || debugArg; - const port = Number(portStr) || (!isBuild ? defaultBuildPort : null); + const port = Number(portStr) || (!isBuilt ? defaultBuildPort : null); const brk = port ? Boolean(!!debugBrkArg) : false; let env: Record | undefined; if (environmentString) { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 41454d12050..d972185f033 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -128,6 +128,8 @@ export const OPTIONS: OptionDescriptions> = { 'inspect-brk-ptyhost': { type: 'string', allowEmptyValue: true }, 'inspect-search': { type: 'string', deprecates: ['debugSearch'], allowEmptyValue: true }, 'inspect-brk-search': { type: 'string', deprecates: ['debugBrkSearch'], allowEmptyValue: true }, + 'inspect-sharedprocess': { type: 'string', allowEmptyValue: true }, + 'inspect-brk-sharedprocess': { type: 'string', allowEmptyValue: true }, 'export-default-configuration': { type: 'string' }, 'install-source': { type: 'string' }, 'enable-smoke-test-driver': { type: 'boolean' }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index cf59e2f135a..bdd756f429f 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -5,7 +5,8 @@ import { homedir, tmpdir } from 'os'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; +import { IDebugParams } from 'vs/platform/environment/common/environment'; +import { AbstractNativeEnvironmentService, parseDebugParams } from 'vs/platform/environment/common/environmentService'; import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -19,3 +20,11 @@ export class NativeEnvironmentService extends AbstractNativeEnvironmentService { }, productService); } } + +export function parsePtyHostDebugPort(args: NativeParsedArgs, isBuild: boolean): IDebugParams { + return parseDebugParams(args['inspect-ptyhost'], args['inspect-brk-ptyhost'], 5877, isBuild, args.extensionEnvironment); +} + +export function parseSharedProcessDebugPort(args: NativeParsedArgs, isBuild: boolean): IDebugParams { + return parseDebugParams(args['inspect-sharedprocess'], args['inspect-brk-sharedprocess'], 5879, isBuild, args.extensionEnvironment); +} diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index 183719c5fdd..85cb855f1c9 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { parseExtensionHostPort } from 'vs/platform/environment/common/environmentService'; +import { parseExtensionHostDebugPort } from 'vs/platform/environment/common/environmentService'; import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import product from 'vs/platform/product/common/product'; @@ -12,7 +12,7 @@ import product from 'vs/platform/product/common/product'; suite('EnvironmentService', () => { test('parseExtensionHostPort when built', () => { - const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a, OPTIONS), true); + const parse = (a: string[]) => parseExtensionHostDebugPort(parseArgs(a, OPTIONS), true); assert.deepStrictEqual(parse([]), { port: null, break: false, env: undefined, debugId: undefined }); assert.deepStrictEqual(parse(['--debugPluginHost']), { port: null, break: false, env: undefined, debugId: undefined }); @@ -30,7 +30,7 @@ suite('EnvironmentService', () => { }); test('parseExtensionHostPort when unbuilt', () => { - const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a, OPTIONS), false); + const parse = (a: string[]) => parseExtensionHostDebugPort(parseArgs(a, OPTIONS), false); assert.deepStrictEqual(parse([]), { port: 5870, break: false, env: undefined, debugId: undefined }); assert.deepStrictEqual(parse(['--debugPluginHost']), { port: 5870, break: false, env: undefined, debugId: undefined }); diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index 5bf5f663738..fe838c2ba54 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -118,6 +118,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter this._getExtHost(id).start({ ...opts, type: 'extensionHost', + entryPoint: 'vs/workbench/api/node/extensionHostProcess', args: ['--skipWorkspaceStorageLock'], execArgv: opts.execArgv, allowLoadingUnsignedLibraries: true, diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index c6da7d50b84..efecede2fea 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -28,8 +28,8 @@ import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService' import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { deepClone } from 'vs/base/common/objects'; -import { removeDangerousEnvVariables } from 'vs/base/common/processes'; +import { canUseUtilityProcess } from 'vs/base/parts/sandbox/electron-main/electronTypes'; +import { parseSharedProcessDebugPort } from 'vs/platform/environment/node/environmentService'; export class SharedProcess extends Disposable implements ISharedProcess { @@ -42,7 +42,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { private windowCloseListener: ((event: ElectronEvent) => void) | undefined = undefined; private utilityProcess: UtilityProcess | undefined = undefined; - private readonly useUtilityProcess = this.configurationService.getValue('window.experimental.sharedProcessUseUtilityProcess'); + private readonly useUtilityProcess = canUseUtilityProcess && this.configurationService.getValue('window.experimental.sharedProcessUseUtilityProcess'); constructor( private readonly machineId: string, @@ -60,6 +60,10 @@ export class SharedProcess extends Disposable implements ISharedProcess { super(); this.registerListeners(); + + if (this.useUtilityProcess) { + this.logService.info('[SharedProcess] using utility process'); + } } private registerListeners(): void { @@ -75,7 +79,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } private async onWindowConnection(e: IpcMainEvent, nonce: string): Promise { - this.logService.trace('SharedProcess: on vscode:createSharedProcessMessageChannel'); + this.logService.trace('[SharedProcess] on vscode:createSharedProcessMessageChannel'); // release barrier if this is the first window connection if (!this.firstWindowConnectionBarrier.isOpen()) { @@ -105,7 +109,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } private onWorkerConnection(e: IpcMainEvent, configuration: IUtilityProcessWorkerConfiguration): void { - this.logService.trace('SharedProcess: onWorkerConnection', configuration); + this.logService.trace('[SharedProcess] onWorkerConnection', configuration); const disposables = new DisposableStore(); @@ -114,7 +118,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { return; // the shared process is already gone, no need to dispose anything } - this.logService.trace(`SharedProcess: disposing worker (reason: '${reason}')`, configuration); + this.logService.trace(`[SharedProcess] disposing worker (reason: '${reason}')`, configuration); // Only once! disposables.dispose(); @@ -142,7 +146,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } private onWillShutdown(): void { - this.logService.trace('SharedProcess: onWillShutdown'); + this.logService.trace('[SharedProcess] onWillShutdown'); if (this.utilityProcess) { this.utilityProcess.postMessage('vscode:electron-main->shared-process=exit'); @@ -171,10 +175,10 @@ export class SharedProcess extends Disposable implements ISharedProcess { // Electron seems to crash on Windows without this setTimeout :| setTimeout(() => { try { - this.logService.trace('SharedProcess: onWillShutdown window.close()'); + this.logService.trace('[SharedProcess] onWillShutdown window.close()'); window.close(); } catch (error) { - this.logService.trace(`SharedProcess: onWillShutdown window.close() error: ${error}`); // ignore, as electron is already shutting down + this.logService.trace(`[SharedProcess] onWillShutdown window.close() error: ${error}`); // ignore, as electron is already shutting down } this.window = undefined; @@ -214,7 +218,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } await whenReady.p; - this.logService.info('SharedProcess: IPC ready'); + this.logService.trace('[SharedProcess] Overall ready'); })(); } @@ -241,7 +245,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } await sharedProcessIpcReady.p; - this.logService.info('SharedProcess: IPC ready'); + this.logService.trace('[SharedProcess] IPC ready'); })(); } @@ -264,29 +268,25 @@ export class SharedProcess extends Disposable implements ISharedProcess { private createUtilityProcess(): void { this.utilityProcess = this._register(new UtilityProcess(this.logService, NullTelemetryService, this.lifecycleMainService)); + const inspectParams = parseSharedProcessDebugPort(this.environmentMainService.args, this.environmentMainService.isBuilt); + let execArgv: string[] | undefined = undefined; + if (inspectParams.port) { + execArgv = ['--nolazy']; + if (inspectParams.break) { + execArgv.push(`--inspect-brk=${inspectParams.port}`); + } else { + execArgv.push(`--inspect=${inspectParams.port}`); + } + } + this.utilityProcess.start({ type: 'shared-process', + entryPoint: 'vs/code/electron-browser/sharedProcess/sharedProcessMain', payload: this.createSharedProcessConfiguration(), - env: this.getEnv(), - execArgv: (!this.environmentMainService.isBuilt || this.environmentMainService.verbose) ? ['--nolazy', '--inspect=5896'] : undefined, // TODO@bpasero this make configurable + execArgv }); } - private getEnv(): NodeJS.ProcessEnv { - const env: NodeJS.ProcessEnv = { - ...deepClone(process.env), - VSCODE_AMD_ENTRYPOINT: 'vs/code/electron-browser/sharedProcess/sharedProcessMain', - VSCODE_PIPE_LOGGING: 'true', - VSCODE_VERBOSE_LOGGING: 'true', - VSCODE_PARENT_PID: String(process.pid) - }; - - // Sanitize environment - removeDangerousEnvVariables(env); - - return env; - } - private createWindow(): void { const configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); @@ -401,6 +401,10 @@ export class SharedProcess extends Disposable implements ISharedProcess { return this.window?.isVisible() ?? false; } + usingUtilityProcess(): boolean { + return !!this.utilityProcess; + } + private isWindowAlive(): boolean { const window = this.window; if (!window) { diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index ee5790c59cc..3c019f0d160 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -11,7 +11,7 @@ import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { parsePtyHostPort } from 'vs/platform/environment/common/environmentService'; +import { parsePtyHostDebugPort } from 'vs/platform/environment/node/environmentService'; import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { RequestStore } from 'vs/platform/terminal/common/requestStore'; @@ -149,7 +149,7 @@ export class PtyHostService extends Disposable implements IPtyService { } }; - const ptyHostDebug = parsePtyHostPort(this._environmentService.args, this._environmentService.isBuilt); + const ptyHostDebug = parsePtyHostDebugPort(this._environmentService.args, this._environmentService.isBuilt); if (ptyHostDebug) { if (ptyHostDebug.break && ptyHostDebug.port) { opts.debugBrk = ptyHostDebug.port; diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 942946307e9..2321092b243 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -15,6 +15,8 @@ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import Severity from 'vs/base/common/severity'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { removeDangerousEnvVariables } from 'vs/base/common/processes'; +import { deepClone } from 'vs/base/common/objects'; export interface IUtilityProcessConfiguration { @@ -23,6 +25,11 @@ export interface IUtilityProcessConfiguration { */ readonly type: string; + /** + * The entry point to load in the utility process. + */ + readonly entryPoint: string; + /** * An optional serializable object to be sent into the utility process * as first message alongside the message port. @@ -55,6 +62,13 @@ export interface IUtilityProcessConfiguration { * with other components. */ readonly correlationId?: string; + + /** + * Optional pid of the parent process. If set, the + * utility process will be terminated when the parent + * process exits. + */ + readonly parentLifecycleBound?: number; } export interface IWindowUtilityProcessConfiguration extends IUtilityProcessConfiguration { @@ -195,15 +209,7 @@ export class UtilityProcess extends Disposable { const execArgv = [...this.configuration.execArgv ?? [], `--vscode-utility-kind=${this.configuration.type}`]; const allowLoadingUnsignedLibraries = this.configuration.allowLoadingUnsignedLibraries; const stdio = 'pipe'; - - let env: { [key: string]: any } | undefined = this.configuration.env; - if (env) { - env = { ...env }; // make a copy since we may be going to mutate it - - for (const key of Object.keys(env)) { - env[key] = String(env[key]); // make sure all values are strings, otherwise the process will not start - } - } + const env = this.createEnv(configuration); this.log('creating new...', Severity.Info); @@ -222,6 +228,26 @@ export class UtilityProcess extends Disposable { return true; } + private createEnv(configuration: IUtilityProcessConfiguration): { [key: string]: any } { + const env: { [key: string]: any } = configuration.env ? { ...configuration.env } : { ...deepClone(process.env) }; + + // Apply support environment variables from config + env['VSCODE_AMD_ENTRYPOINT'] = configuration.entryPoint; + if (typeof configuration.parentLifecycleBound === 'number') { + env['VSCODE_PARENT_PID'] = String(configuration.parentLifecycleBound); + } + + // Remove any environment variables that are not allowed + removeDangerousEnvVariables(env); + + // Ensure all values are strings, otherwise the process will not start + for (const key of Object.keys(env)) { + env[key] = String(env[key]); + } + + return env; + } + private registerListeners(process: UtilityProcessProposedApi.UtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string, isWindowSandboxed: boolean): void { // Stdout diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts index 30848510d7f..b5e54ffa725 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts @@ -10,8 +10,6 @@ import { IUtilityProcessWorkerCreateConfiguration, IOnDidTerminateUtilityrocessW import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { WindowUtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { deepClone } from 'vs/base/common/objects'; -import { removeDangerousEnvVariables } from 'vs/base/common/processes'; import { hash } from 'vs/base/common/hash'; import { Event, Emitter } from 'vs/base/common/event'; import { DeferredPromise } from 'vs/base/common/async'; @@ -114,32 +112,21 @@ class UtilityProcessWorker extends Disposable { } spawn(): boolean { + const window = this.windowsMainService.getWindowById(this.configuration.reply.windowId); + const windowPid = window?.win?.webContents.getOSProcessId(); + return this.utilityProcess.start({ + type: this.configuration.process.type, + entryPoint: this.configuration.process.moduleId, + parentLifecycleBound: windowPid, windowLifecycleBound: true, correlationId: `${this.configuration.reply.windowId}`, responseWindowId: this.configuration.reply.windowId, responseChannel: this.configuration.reply.channel, - responseNonce: this.configuration.reply.nonce, - type: this.configuration.process.type, - env: this.getEnv() + responseNonce: this.configuration.reply.nonce }); } - private getEnv(): NodeJS.ProcessEnv { - const env: NodeJS.ProcessEnv = { - ...deepClone(process.env), - VSCODE_AMD_ENTRYPOINT: this.configuration.process.moduleId, - VSCODE_PIPE_LOGGING: 'true', - VSCODE_VERBOSE_LOGGING: 'true', - VSCODE_PARENT_PID: String(process.pid) - }; - - // Sanitize environment - removeDangerousEnvVariables(env); - - return env; - } - kill() { this.utilityProcess.kill(); } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index cedf9f5121c..62fa52e0cbb 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -54,6 +54,7 @@ import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataPro import { IPolicyService } from 'vs/platform/policy/common/policy'; import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; +import { canUseUtilityProcess } from 'vs/base/parts/sandbox/electron-main/electronTypes'; //#region Helper Interfaces @@ -1389,7 +1390,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic policiesData: this.policyService.serialize(), continueOn: this.environmentMainService.continueOn, - preferUtilityProcess: filesConfig?.experimental?.watcherUseUtilityProcess ?? windowConfig?.experimental?.sharedProcessUseUtilityProcess ?? false + preferUtilityProcess: canUseUtilityProcess ? (filesConfig?.experimental?.watcherUseUtilityProcess ?? windowConfig?.experimental?.sharedProcessUseUtilityProcess ?? false) : false }; // New window diff --git a/src/vs/workbench/electron-sandbox/actions/developerActions.ts b/src/vs/workbench/electron-sandbox/actions/developerActions.ts index 660ad2206e8..75959edb375 100644 --- a/src/vs/workbench/electron-sandbox/actions/developerActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/developerActions.ts @@ -68,7 +68,6 @@ export class ConfigureRuntimeArgumentsAction extends Action2 { } } - export class ToggleSharedProcessAction extends Action2 { constructor() { @@ -130,5 +129,3 @@ export class OpenUserDataFolderAction extends Action2 { return nativeHostService.showItemInFolder(itemToShow.fsPath); } } - - From b4db415a10aa5749cb8322ee3d96b6f91345815c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 16 Feb 2023 15:30:46 +0100 Subject: [PATCH 57/85] cdn: only gzip encode known mimetypes (#174577) --- build/azure-pipelines/upload-cdn.js | 114 +++++++++++++++++++------- build/azure-pipelines/upload-cdn.ts | 122 +++++++++++++++++++++------- 2 files changed, 180 insertions(+), 56 deletions(-) diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index 287d2b0e403..5a218735d3f 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -9,51 +9,109 @@ const Vinyl = require("vinyl"); const vfs = require("vinyl-fs"); const filter = require("gulp-filter"); const gzip = require("gulp-gzip"); +const mime = require("mime"); const identity_1 = require("@azure/identity"); const azure = require('gulp-azure-storage'); const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); +mime.define({ + 'application/typescript': ['ts'], + 'application/json': ['code-snippets'], +}); +// From default AFD configuration +const MimeTypesToCompress = new Set([ + 'application/eot', + 'application/font', + 'application/font-sfnt', + 'application/javascript', + 'application/json', + 'application/opentype', + 'application/otf', + 'application/pkcs7-mime', + 'application/truetype', + 'application/ttf', + 'application/typescript', + 'application/vnd.ms-fontobject', + 'application/xhtml+xml', + 'application/xml', + 'application/xml+rss', + 'application/x-font-opentype', + 'application/x-font-truetype', + 'application/x-font-ttf', + 'application/x-httpd-cgi', + 'application/x-javascript', + 'application/x-mpegurl', + 'application/x-opentype', + 'application/x-otf', + 'application/x-perl', + 'application/x-ttf', + 'font/eot', + 'font/ttf', + 'font/otf', + 'font/opentype', + 'image/svg+xml', + 'text/css', + 'text/csv', + 'text/html', + 'text/javascript', + 'text/js', + 'text/markdown', + 'text/plain', + 'text/richtext', + 'text/tab-separated-values', + 'text/xml', + 'text/x-script', + 'text/x-component', + 'text/x-java-source' +]); +function wait(stream) { + return new Promise((c, e) => { + stream.on('end', () => c()); + stream.on('error', (err) => e(err)); + }); +} async function main() { const files = []; - const options = { + const options = (compressed) => ({ account: process.env.AZURE_STORAGE_ACCOUNT, credential, container: process.env.VSCODE_QUALITY, prefix: commit + '/', contentSettings: { - contentEncoding: 'gzip', + contentEncoding: compressed ? 'gzip' : undefined, cacheControl: 'max-age=31536000, public' } - }; - await new Promise((c, e) => { - vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) - .pipe(filter(f => !f.isDirectory())) - .pipe(gzip({ append: false })) - .pipe(es.through(function (data) { - console.log('Uploading:', data.relative); // debug - files.push(data.relative); - this.emit('data', data); - })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err) => e(err)); }); - await new Promise((c, e) => { - const listing = new Vinyl({ - path: 'files.txt', - contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } - }); - console.log(`Uploading: files.txt (${files.length} files)`); // debug - es.readArray([listing]) - .pipe(gzip({ append: false })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err) => e(err)); + const all = vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) + .pipe(filter(f => !f.isDirectory())); + const compressed = all + .pipe(filter(f => MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + const uncompressed = all + .pipe(filter(f => !MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(azure.upload(options(false))); + const out = es.merge(compressed, uncompressed) + .pipe(es.through(function (f) { + console.log('Uploaded:', f.relative); + files.push(f.relative); + this.emit('data', f); + })); + console.log(`Uploading files to CDN...`); // debug + await wait(out); + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } }); + const filesOut = es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + console.log(`Uploading: files.txt (${files.length} files)`); // debug + await wait(filesOut); } main().catch(err => { console.error(err); process.exit(1); }); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLWNkbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInVwbG9hZC1jZG4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxtQ0FBbUM7QUFDbkMsK0JBQStCO0FBQy9CLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsa0NBQWtDO0FBQ2xDLDhDQUF5RDtBQUN6RCxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQztBQUU1QyxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLHNCQUFzQixDQUFDLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBQ3pGLE1BQU0sVUFBVSxHQUFHLElBQUksaUNBQXNCLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBRSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixDQUFFLENBQUMsQ0FBQztBQUVySixLQUFLLFVBQVUsSUFBSTtJQUNsQixNQUFNLEtBQUssR0FBYSxFQUFFLENBQUM7SUFDM0IsTUFBTSxPQUFPLEdBQUc7UUFDZixPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUI7UUFDMUMsVUFBVTtRQUNWLFNBQVMsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWM7UUFDckMsTUFBTSxFQUFFLE1BQU0sR0FBRyxHQUFHO1FBQ3BCLGVBQWUsRUFBRTtZQUNoQixlQUFlLEVBQUUsTUFBTTtZQUN2QixZQUFZLEVBQUUsMEJBQTBCO1NBQ3hDO0tBQ0QsQ0FBQztJQUVGLE1BQU0sSUFBSSxPQUFPLENBQU8sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7UUFDaEMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsRUFBRSxHQUFHLEVBQUUsZUFBZSxFQUFFLElBQUksRUFBRSxlQUFlLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxDQUFDO2FBQ3ZFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO2FBQ25DLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQzthQUM3QixJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxVQUFVLElBQVc7WUFDckMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsUUFBUTtZQUNsRCxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUMxQixJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUN6QixDQUFDLENBQUMsQ0FBQzthQUNGLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDO2FBQzNCLEVBQUUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUM7YUFDcEIsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLEdBQVEsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDckMsQ0FBQyxDQUFDLENBQUM7SUFFSCxNQUFNLElBQUksT0FBTyxDQUFPLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ2hDLE1BQU0sT0FBTyxHQUFHLElBQUksS0FBSyxDQUFDO1lBQ3pCLElBQUksRUFBRSxXQUFXO1lBQ2pCLFFBQVEsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDdkMsSUFBSSxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBUztTQUM1QixDQUFDLENBQUM7UUFFSCxPQUFPLENBQUMsR0FBRyxDQUFDLHlCQUF5QixLQUFLLENBQUMsTUFBTSxTQUFTLENBQUMsQ0FBQyxDQUFDLFFBQVE7UUFDckUsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDO2FBQ3JCLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQzthQUM3QixJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQzthQUMzQixFQUFFLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDO2FBQ3BCLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFRLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3JDLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRTtJQUNsQixPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ25CLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakIsQ0FBQyxDQUFDLENBQUMifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLWNkbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInVwbG9hZC1jZG4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxtQ0FBbUM7QUFDbkMsK0JBQStCO0FBQy9CLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsa0NBQWtDO0FBQ2xDLDZCQUE2QjtBQUM3Qiw4Q0FBeUQ7QUFDekQsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFFNUMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUMsQ0FBQztBQUN6RixNQUFNLFVBQVUsR0FBRyxJQUFJLGlDQUFzQixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBRSxDQUFDLENBQUM7QUFFckosSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUNYLHdCQUF3QixFQUFFLENBQUMsSUFBSSxDQUFDO0lBQ2hDLGtCQUFrQixFQUFFLENBQUMsZUFBZSxDQUFDO0NBQ3JDLENBQUMsQ0FBQztBQUVILGlDQUFpQztBQUNqQyxNQUFNLG1CQUFtQixHQUFHLElBQUksR0FBRyxDQUFDO0lBQ25DLGlCQUFpQjtJQUNqQixrQkFBa0I7SUFDbEIsdUJBQXVCO0lBQ3ZCLHdCQUF3QjtJQUN4QixrQkFBa0I7SUFDbEIsc0JBQXNCO0lBQ3RCLGlCQUFpQjtJQUNqQix3QkFBd0I7SUFDeEIsc0JBQXNCO0lBQ3RCLGlCQUFpQjtJQUNqQix3QkFBd0I7SUFDeEIsK0JBQStCO0lBQy9CLHVCQUF1QjtJQUN2QixpQkFBaUI7SUFDakIscUJBQXFCO0lBQ3JCLDZCQUE2QjtJQUM3Qiw2QkFBNkI7SUFDN0Isd0JBQXdCO0lBQ3hCLHlCQUF5QjtJQUN6QiwwQkFBMEI7SUFDMUIsdUJBQXVCO0lBQ3ZCLHdCQUF3QjtJQUN4QixtQkFBbUI7SUFDbkIsb0JBQW9CO0lBQ3BCLG1CQUFtQjtJQUNuQixVQUFVO0lBQ1YsVUFBVTtJQUNWLFVBQVU7SUFDVixlQUFlO0lBQ2YsZUFBZTtJQUNmLFVBQVU7SUFDVixVQUFVO0lBQ1YsV0FBVztJQUNYLGlCQUFpQjtJQUNqQixTQUFTO0lBQ1QsZUFBZTtJQUNmLFlBQVk7SUFDWixlQUFlO0lBQ2YsMkJBQTJCO0lBQzNCLFVBQVU7SUFDVixlQUFlO0lBQ2Ysa0JBQWtCO0lBQ2xCLG9CQUFvQjtDQUNwQixDQUFDLENBQUM7QUFFSCxTQUFTLElBQUksQ0FBQyxNQUF3QjtJQUNyQyxPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ2pDLE1BQU0sQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDNUIsTUFBTSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFRLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQzFDLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELEtBQUssVUFBVSxJQUFJO0lBQ2xCLE1BQU0sS0FBSyxHQUFhLEVBQUUsQ0FBQztJQUMzQixNQUFNLE9BQU8sR0FBRyxDQUFDLFVBQW1CLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDekMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCO1FBQzFDLFVBQVU7UUFDVixTQUFTLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjO1FBQ3JDLE1BQU0sRUFBRSxNQUFNLEdBQUcsR0FBRztRQUNwQixlQUFlLEVBQUU7WUFDaEIsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxTQUFTO1lBQ2hELFlBQVksRUFBRSwwQkFBMEI7U0FDeEM7S0FDRCxDQUFDLENBQUM7SUFFSCxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxlQUFlLEVBQUUsSUFBSSxFQUFFLGVBQWUsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLENBQUM7U0FDbkYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUV0QyxNQUFNLFVBQVUsR0FBRyxHQUFHO1NBQ3BCLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQy9ELElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztTQUM3QixJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRXBDLE1BQU0sWUFBWSxHQUFHLEdBQUc7U0FDdEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsbUJBQW1CLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUNoRSxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRXJDLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUFFLFlBQVksQ0FBQztTQUM1QyxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7UUFDM0IsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3JDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3RCLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFTCxPQUFPLENBQUMsR0FBRyxDQUFDLDJCQUEyQixDQUFDLENBQUMsQ0FBQyxRQUFRO0lBQ2xELE1BQU0sSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBRWhCLE1BQU0sT0FBTyxHQUFHLElBQUksS0FBSyxDQUFDO1FBQ3pCLElBQUksRUFBRSxXQUFXO1FBQ2pCLFFBQVEsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkMsSUFBSSxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBUztLQUM1QixDQUFDLENBQUM7SUFFSCxNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUM7U0FDdEMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1NBQzdCLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFcEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyx5QkFBeUIsS0FBSyxDQUFDLE1BQU0sU0FBUyxDQUFDLENBQUMsQ0FBQyxRQUFRO0lBQ3JFLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3RCLENBQUM7QUFFRCxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUU7SUFDbEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNuQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2pCLENBQUMsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index ddb03c1e839..753d63cb85d 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -8,53 +8,119 @@ import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; import * as filter from 'gulp-filter'; import * as gzip from 'gulp-gzip'; +import * as mime from 'mime'; import { ClientSecretCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); +mime.define({ + 'application/typescript': ['ts'], + 'application/json': ['code-snippets'], +}); + +// From default AFD configuration +const MimeTypesToCompress = new Set([ + 'application/eot', + 'application/font', + 'application/font-sfnt', + 'application/javascript', + 'application/json', + 'application/opentype', + 'application/otf', + 'application/pkcs7-mime', + 'application/truetype', + 'application/ttf', + 'application/typescript', + 'application/vnd.ms-fontobject', + 'application/xhtml+xml', + 'application/xml', + 'application/xml+rss', + 'application/x-font-opentype', + 'application/x-font-truetype', + 'application/x-font-ttf', + 'application/x-httpd-cgi', + 'application/x-javascript', + 'application/x-mpegurl', + 'application/x-opentype', + 'application/x-otf', + 'application/x-perl', + 'application/x-ttf', + 'font/eot', + 'font/ttf', + 'font/otf', + 'font/opentype', + 'image/svg+xml', + 'text/css', + 'text/csv', + 'text/html', + 'text/javascript', + 'text/js', + 'text/markdown', + 'text/plain', + 'text/richtext', + 'text/tab-separated-values', + 'text/xml', + 'text/x-script', + 'text/x-component', + 'text/x-java-source' +]); + +function wait(stream: es.ThroughStream): Promise { + return new Promise((c, e) => { + stream.on('end', () => c()); + stream.on('error', (err: any) => e(err)); + }); +} + async function main(): Promise { const files: string[] = []; - const options = { + const options = (compressed: boolean) => ({ account: process.env.AZURE_STORAGE_ACCOUNT, credential, container: process.env.VSCODE_QUALITY, prefix: commit + '/', contentSettings: { - contentEncoding: 'gzip', + contentEncoding: compressed ? 'gzip' : undefined, cacheControl: 'max-age=31536000, public' } - }; - - await new Promise((c, e) => { - vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) - .pipe(filter(f => !f.isDirectory())) - .pipe(gzip({ append: false })) - .pipe(es.through(function (data: Vinyl) { - console.log('Uploading:', data.relative); // debug - files.push(data.relative); - this.emit('data', data); - })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err: any) => e(err)); }); - await new Promise((c, e) => { - const listing = new Vinyl({ - path: 'files.txt', - contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } as any - }); + const all = vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) + .pipe(filter(f => !f.isDirectory())); - console.log(`Uploading: files.txt (${files.length} files)`); // debug - es.readArray([listing]) - .pipe(gzip({ append: false })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err: any) => e(err)); + const compressed = all + .pipe(filter(f => MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + + const uncompressed = all + .pipe(filter(f => !MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(azure.upload(options(false))); + + const out = es.merge(compressed, uncompressed) + .pipe(es.through(function (f) { + console.log('Uploaded:', f.relative); + files.push(f.relative); + this.emit('data', f); + })); + + console.log(`Uploading files to CDN...`); // debug + await wait(out); + + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } as any }); + + const filesOut = es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + + console.log(`Uploading: files.txt (${files.length} files)`); // debug + await wait(filesOut); } main().catch(err => { From d4b10110da10a29fce3d79a804067b4204baddba Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 16 Feb 2023 15:35:19 +0100 Subject: [PATCH 58/85] Fixes https://github.com/microsoft/monaco-editor/issues/3564 (#174578) --- src/vs/editor/contrib/format/browser/formatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index 934d5f4a6cc..2abca269100 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -40,7 +40,6 @@ class FormatOnType implements IEditorContribution { @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IEditorWorkerService private readonly _workerService: IEditorWorkerService ) { - this._disposables.add(_languageFeaturesService.onTypeFormattingEditProvider.onDidChange(this._update, this)); this._disposables.add(_editor.onDidChangeModel(() => this._update())); this._disposables.add(_editor.onDidChangeModelLanguage(() => this._update())); @@ -49,6 +48,7 @@ class FormatOnType implements IEditorContribution { this._update(); } })); + this._update(); } dispose(): void { From 6da385b3a3b315b10308a99f693708c403735524 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 16 Feb 2023 16:15:10 +0100 Subject: [PATCH 59/85] Multi quick diff polish (#174584) Part of https://github.com/microsoft/vscode/issues/169012 --- extensions/git/package.json | 1 + extensions/git/src/repository.ts | 7 +++++++ src/vs/workbench/api/browser/mainThreadSCM.ts | 6 +++++- src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 5 ++++- src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts | 7 +------ src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts | 4 ++++ 7 files changed, 23 insertions(+), 8 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c7d4f94cfa2..a6ce8854c93 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -15,6 +15,7 @@ "contribEditSessions", "contribViewsWelcome", "editSessionIdentityProvider", + "quickDiffProvider", "scmActionButton", "scmSelectedProvider", "scmValidation", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 81e8651f846..4a12fb8eb2b 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -960,6 +960,13 @@ export class Repository implements Disposable { } } + /** + * Quick diff label + */ + get label(): string { + return l10n.t('Git local working changes'); + } + provideOriginalResource(uri: Uri): Uri | undefined { if (uri.scheme !== 'file') { return; diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index ea613cde683..f75b82bee9f 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -158,7 +158,11 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { } if (features.hasQuickDiffProvider && !this._quickDiff) { - this._quickDiff = this._quickDiffService.addQuickDiffProvider(this); + this._quickDiff = this._quickDiffService.addQuickDiffProvider({ + label: features.quickDiffLabel ?? this.label, + rootUri: this.rootUri, + getOriginalResource: (uri: URI) => this.getOriginalResource(uri) + }); } else if (features.hasQuickDiffProvider === false && this._quickDiff) { this._quickDiff.dispose(); this._quickDiff = undefined; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 414c984240f..db3d3eccec1 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1179,6 +1179,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { export interface SCMProviderFeatures { hasQuickDiffProvider?: boolean; + quickDiffLabel?: string; count?: number; commitTemplate?: string; acceptInputCommand?: languages.Command; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 147ea6be023..86426a0df86 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -488,7 +488,10 @@ class ExtHostSourceControl implements vscode.SourceControl { set quickDiffProvider(quickDiffProvider: vscode.QuickDiffProvider | undefined) { this._quickDiffProvider = quickDiffProvider; - this.#proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider }); + if (quickDiffProvider?.label) { + checkProposedApiEnabled(this._extension, 'quickDiffProvider'); + } + this.#proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider, quickDiffLabel: quickDiffProvider?.label }); } private _commitTemplate: string | undefined = undefined; diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index a2dbbbe0fa2..7b8dc5d1652 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -235,7 +235,6 @@ class DirtyDiffWidget extends PeekViewWidget { const change = labeledChange.change; this._index = index; this._provider = labeledChange.label; - const previousVisibleRange = this.visibleRange; this.change = change; const originalModel = this.model.original; @@ -248,7 +247,7 @@ class DirtyDiffWidget extends PeekViewWidget { // TODO@joao TODO@alex need this setTimeout probably because the // non-side-by-side diff still hasn't created the view zones - onFirstDiffUpdate(() => setTimeout(() => (usePosition || !previousVisibleRange) ? this.revealChange(change) : this.revealLines(previousVisibleRange), 0)); + onFirstDiffUpdate(() => setTimeout(() => this.revealChange(change), 0)); const diffEditorModel = this.model.getDiffEditorModel(labeledChange.uri.toString()); if (!diffEditorModel) { @@ -437,10 +436,6 @@ class DirtyDiffWidget extends PeekViewWidget { this.diffEditor.revealLinesInCenter(start, end, ScrollType.Immediate); } - private revealLines(range: Range): void { - this.diffEditor.revealLinesInCenter(range.startLineNumber, range.endLineNumber, ScrollType.Immediate); - } - private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekViewBorder) || Color.transparent; this.style({ diff --git a/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts b/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts index c0c0078f561..3778e7092eb 100644 --- a/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts @@ -10,4 +10,8 @@ declare module 'vscode' { export namespace window { export function registerQuickDiffProvider(selector: DocumentSelector, quickDiffProvider: QuickDiffProvider, label: string, rootUri?: Uri): Disposable; } + + interface QuickDiffProvider { + label?: string; + } } From 713d6e2a603ff220e14c0479f9e340e835a916a5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 16 Feb 2023 16:43:16 +0100 Subject: [PATCH 60/85] Properly dispose quick diff providers (#174586) --- src/vs/workbench/api/common/extHostQuickDiff.ts | 5 ++++- .../workbench/contrib/scm/browser/dirtydiffDecorator.ts | 1 + src/vs/workbench/contrib/scm/common/quickDiff.ts | 2 ++ src/vs/workbench/contrib/scm/common/quickDiffService.ts | 9 +++++++-- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostQuickDiff.ts b/src/vs/workbench/api/common/extHostQuickDiff.ts index 68cce87bc2d..04e7c04a16c 100644 --- a/src/vs/workbench/api/common/extHostQuickDiff.ts +++ b/src/vs/workbench/api/common/extHostQuickDiff.ts @@ -41,7 +41,10 @@ export class ExtHostQuickDiff implements ExtHostQuickDiffShape { this.providers.set(handle, quickDiffProvider); this.proxy.$registerQuickDiffProvider(handle, DocumentSelector.from(selector, this.uriTransformer), label, rootUri); return { - dispose: () => this.providers.delete(handle) + dispose: () => { + this.proxy.$unregisterQuickDiffProvider(handle); + this.providers.delete(handle); + } }; } } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 7b8dc5d1652..212a40fe506 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -1253,6 +1253,7 @@ export class DirtyDiffModel extends Disposable { this.triggerDiff(); })); + this._register(this.quickDiffService.onDidChangeQuickDiffProviders(() => this.triggerDiff())); this.triggerDiff(); } diff --git a/src/vs/workbench/contrib/scm/common/quickDiff.ts b/src/vs/workbench/contrib/scm/common/quickDiff.ts index 083dca3349c..34caf0865dd 100644 --- a/src/vs/workbench/contrib/scm/common/quickDiff.ts +++ b/src/vs/workbench/contrib/scm/common/quickDiff.ts @@ -7,6 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; +import { Event } from 'vs/base/common/event'; export const IQuickDiffService = createDecorator('quickDiff'); @@ -25,6 +26,7 @@ export interface QuickDiff { export interface IQuickDiffService { readonly _serviceBrand: undefined; + readonly onDidChangeQuickDiffProviders: Event; addQuickDiffProvider(quickDiff: QuickDiffProvider): IDisposable; getQuickDiffs(uri: URI, language?: string, isSynchronized?: boolean): Promise; } diff --git a/src/vs/workbench/contrib/scm/common/quickDiffService.ts b/src/vs/workbench/contrib/scm/common/quickDiffService.ts index bc87e51cc0e..384cbcf1a5d 100644 --- a/src/vs/workbench/contrib/scm/common/quickDiffService.ts +++ b/src/vs/workbench/contrib/scm/common/quickDiffService.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IQuickDiffService, QuickDiff, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; import { isEqualOrParent } from 'vs/base/common/resources'; import { score } from 'vs/editor/common/languageSelector'; +import { Emitter } from 'vs/base/common/event'; function createProviderComparer(uri: URI): (a: QuickDiffProvider, b: QuickDiffProvider) => number { return (a, b) => { @@ -34,16 +35,20 @@ function createProviderComparer(uri: URI): (a: QuickDiffProvider, b: QuickDiffPr }; } -export class QuickDiffService implements IQuickDiffService { +export class QuickDiffService extends Disposable implements IQuickDiffService { declare readonly _serviceBrand: undefined; private quickDiffProviders: Set = new Set(); + private readonly _onDidChangeQuickDiffProviders = this._register(new Emitter()); + readonly onDidChangeQuickDiffProviders = this._onDidChangeQuickDiffProviders.event; addQuickDiffProvider(quickDiff: QuickDiffProvider): IDisposable { this.quickDiffProviders.add(quickDiff); + this._onDidChangeQuickDiffProviders.fire(); return { dispose: () => { this.quickDiffProviders.delete(quickDiff); + this._onDidChangeQuickDiffProviders.fire(); } }; } From a28dc84d88c837dc87bedc360b48b6de9c10dba2 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Thu, 16 Feb 2023 08:30:16 -0800 Subject: [PATCH 61/85] warn on CG alerts (#174591) --- build/azure-pipelines/product-compile.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index e02eaad1bac..9ddcb882d87 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -178,3 +178,4 @@ steps: inputs: sourceScanPath: $(Build.SourcesDirectory) continueOnError: true + alertWarningLevel: 'Medium' From 174600ec07a733c798ef44cbb0e7eed56b048196 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Thu, 16 Feb 2023 08:56:20 -0800 Subject: [PATCH 62/85] fix build (#174596) --- build/azure-pipelines/product-compile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 9ddcb882d87..fe1f55c6f27 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -177,5 +177,5 @@ steps: displayName: "Component Detection" inputs: sourceScanPath: $(Build.SourcesDirectory) + alertWarningLevel: 'Medium' continueOnError: true - alertWarningLevel: 'Medium' From 3bc840737ba7cae85ced0f17067947db653ca637 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 16 Feb 2023 18:18:09 +0100 Subject: [PATCH 63/85] fix #173986 (#174599) --- .../test/browser/extensionEnablementService.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index f298e2c0709..39f06f3c822 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -319,10 +319,10 @@ suite('ExtensionEnablementService Test', () => { assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); - test('test enable an extension globally return truthy promise', () => { - return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) - .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally)) - .then(value => assert.ok(value)); + test('test enable an extension globally return truthy promise', async () => { + await testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally); + const value = await testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally); + assert.strictEqual(value[0], true); }); test('test enable an extension globally triggers change event', () => { From 524f4227ca52b905b526ffcd8e06da9f10991dc6 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 16 Feb 2023 10:18:39 -0700 Subject: [PATCH 64/85] Fix context menu with hover (#174597) --- src/vs/base/browser/ui/iconLabel/iconLabelHover.ts | 5 ++++- .../workbench/contrib/files/browser/views/explorerViewer.ts | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 34625f28787..a36088fccc3 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -164,6 +164,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM let hoverWidget: UpdatableHoverWidget | undefined; const hideHover = (disposeWidget: boolean, disposePreparation: boolean) => { + const hadHover = hoverWidget !== undefined; if (disposeWidget) { hoverWidget?.dispose(); hoverWidget = undefined; @@ -172,7 +173,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM hoverPreparation?.dispose(); hoverPreparation = undefined; } - hoverDelegate.onDidHideHover?.(); + if (hadHover) { + hoverDelegate.onDidHideHover?.(); + } }; const triggerShowHover = (delay: number, focus?: boolean, target?: IHoverDelegateTarget) => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index bca1c18ab2a..30a647243bb 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -285,7 +285,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer Date: Thu, 16 Feb 2023 11:22:28 -0600 Subject: [PATCH 65/85] use monaco editor in terminal accessible buffer (#174400) --- package.json | 4 +- remote/package.json | 4 +- remote/web/package.json | 2 +- remote/web/yarn.lock | 8 +- remote/yarn.lock | 16 +- src/vs/platform/terminal/common/terminal.ts | 3 +- .../contrib/terminal/browser/terminal.ts | 2 +- .../terminal/browser/terminalActions.ts | 2 +- .../terminal/browser/xterm/xtermTerminal.ts | 188 ++++++++++++------ .../contrib/terminal/common/terminal.ts | 1 - .../terminal/common/terminalConfiguration.ts | 11 - yarn.lock | 16 +- 12 files changed, 151 insertions(+), 106 deletions(-) diff --git a/package.json b/package.json index a842f18f4d6..1da790a529a 100644 --- a/package.json +++ b/package.json @@ -87,13 +87,13 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "8.0.0", - "xterm": "5.2.0-beta.28", + "xterm": "5.2.0-beta.29", "xterm-addon-canvas": "0.4.0-beta.7", "xterm-addon-search": "0.11.0", "xterm-addon-serialize": "0.9.0", "xterm-addon-unicode11": "0.5.0", "xterm-addon-webgl": "0.15.0-beta.7", - "xterm-headless": "5.2.0-beta.28", + "xterm-headless": "5.2.0-beta.29", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 1eec4929126..d39cdb49361 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,13 +24,13 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "8.0.0", - "xterm": "5.2.0-beta.28", + "xterm": "5.2.0-beta.29", "xterm-addon-canvas": "0.4.0-beta.7", "xterm-addon-search": "0.11.0", "xterm-addon-serialize": "0.9.0", "xterm-addon-unicode11": "0.5.0", "xterm-addon-webgl": "0.15.0-beta.7", - "xterm-headless": "5.2.0-beta.28", + "xterm-headless": "5.2.0-beta.29", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index ec717aee479..53620ea494a 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,7 +11,7 @@ "tas-client-umd": "0.1.6", "vscode-oniguruma": "1.7.0", "vscode-textmate": "8.0.0", - "xterm": "5.2.0-beta.28", + "xterm": "5.2.0-beta.29", "xterm-addon-canvas": "0.4.0-beta.7", "xterm-addon-search": "0.11.0", "xterm-addon-unicode11": "0.5.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index c5118aaad4b..79830abf89f 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -88,7 +88,7 @@ xterm-addon-webgl@0.15.0-beta.7: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.7.tgz#ab247b499f61e8eebff92e08ec5ca999d87e06af" integrity sha512-7WCI/D6uFNp3y9TeTsbSo1h7gCy4h/yP2lWn8ZEjCaiGvO11DbKMq17fbiwaR3YmGWXoRKkcLaNIiqxFnjKO4w== -xterm@5.2.0-beta.28: - version "5.2.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.28.tgz#852347e4eaf5aae7d82c90592a42adc9daab2a79" - integrity sha512-aLDxCuqjWHjvnhfWfkxy/y6coNrC+QIhbDe2sdfLPkrxhK6KnYE6qiZD5jXUIQXeq0KmSDcYi/esuKujvobC2A== +xterm@5.2.0-beta.29: + version "5.2.0-beta.29" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.29.tgz#99764aff5cd8cdb4335f5d59466b134cfcb45e3e" + integrity sha512-zx5RKcQqo78bza4R/m3WtxAJCBAF4U61fy6cxqb1PkqXF9/qdYlySUCVOauMxv+6n6cAxt3EQWwLlgvbvQBbsw== diff --git a/remote/yarn.lock b/remote/yarn.lock index 29cdd7e8028..efc0ca00a26 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -866,15 +866,15 @@ xterm-addon-webgl@0.15.0-beta.7: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.7.tgz#ab247b499f61e8eebff92e08ec5ca999d87e06af" integrity sha512-7WCI/D6uFNp3y9TeTsbSo1h7gCy4h/yP2lWn8ZEjCaiGvO11DbKMq17fbiwaR3YmGWXoRKkcLaNIiqxFnjKO4w== -xterm-headless@5.2.0-beta.28: - version "5.2.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.28.tgz#d2c149da51ef138f46268b755c4fdc4202eb771c" - integrity sha512-4XcjBhFwuyjpz2ubESwp75UceySOOKdJszKyyxOQ3/7L937uiVEBBLc8T231XU8lSwWUU7czyNjYyCfpszY4+Q== +xterm-headless@5.2.0-beta.29: + version "5.2.0-beta.29" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.29.tgz#dd08312fdb4292c217e685d9e2e8b1957364e298" + integrity sha512-1P4urIeDTkl2C+zGb4WUnKJMACZMPGYHwVXMjkB0WhMISbkt6M34MH9ljxHhnL99dHwlx2Lvi6wvhnpyZucWCg== -xterm@5.2.0-beta.28: - version "5.2.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.28.tgz#852347e4eaf5aae7d82c90592a42adc9daab2a79" - integrity sha512-aLDxCuqjWHjvnhfWfkxy/y6coNrC+QIhbDe2sdfLPkrxhK6KnYE6qiZD5jXUIQXeq0KmSDcYi/esuKujvobC2A== +xterm@5.2.0-beta.29: + version "5.2.0-beta.29" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.29.tgz#99764aff5cd8cdb4335f5d59466b134cfcb45e3e" + integrity sha512-zx5RKcQqo78bza4R/m3WtxAJCBAF4U61fy6cxqb1PkqXF9/qdYlySUCVOauMxv+6n6cAxt3EQWwLlgvbvQBbsw== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index dd90c56776c..56496a8deff 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -112,8 +112,7 @@ export const enum TerminalSettingId { ShellIntegrationDecorationsEnabled = 'terminal.integrated.shellIntegration.decorationsEnabled', ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history', ShellIntegrationSuggestEnabled = 'terminal.integrated.shellIntegration.suggestEnabled', - SmoothScrolling = 'terminal.integrated.smoothScrolling', - AccessibleBufferContentEditable = 'terminal.integrated.accessibleBufferContentEditable' + SmoothScrolling = 'terminal.integrated.smoothScrolling' } export const enum TerminalLogConstants { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 75dee393f73..15d579d37c8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -1053,7 +1053,7 @@ export interface IXtermTerminal { /** * Focuses the accessible buffer, updating its contents */ - focusAccessibleBuffer(): void; + focusAccessibleBuffer(): Promise; } export interface IInternalXtermTerminal { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 847fb588711..31b34991131 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -423,7 +423,7 @@ export function registerTerminalActions() { }); } async run(accessor: ServicesAccessor): Promise { - accessor.get(ITerminalService).activeInstance?.xterm?.focusAccessibleBuffer(); + await accessor.get(ITerminalService).activeInstance?.xterm?.focusAccessibleBuffer(); } }); registerAction2(class extends Action2 { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 04185c84067..d83ddae19c6 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -38,8 +38,16 @@ import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { SuggestAddon } from 'vs/workbench/contrib/terminal/browser/xterm/suggestAddon'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { isLinux } from 'vs/base/common/platform'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { LinkDetector } from 'vs/editor/contrib/links/browser/links'; +import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; const enum RenderConstants { /** @@ -281,7 +289,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II }); } - focusAccessibleBuffer(): void { + async focusAccessibleBuffer(): Promise { this._accessibileBuffer?.focus(); } @@ -312,7 +320,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II if (!this._container) { this.raw.open(container); } - this._accessibileBuffer = this._instantiationService.createInstance(AccessibleBuffer, this.raw, this.getFont(), this._capabilities); + this._accessibileBuffer = this._instantiationService.createInstance(AccessibleBuffer, this, this._capabilities); // TODO: Move before open to the DOM renderer doesn't initialize if (this._shouldLoadWebgl()) { this._enableWebglRenderer(); @@ -763,76 +771,126 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this.raw.write(data); } } - +const enum ACCESSIBLE_BUFFER { Scheme = 'terminal-accessible-buffer' } class AccessibleBuffer extends DisposableStore { - - private _accessibleBuffer: HTMLElement | undefined; - private _bufferElementFragment: DocumentFragment | undefined; + private _accessibleBuffer: HTMLElement; + private _bufferEditor: CodeEditorWidget; + private _editorContainer: HTMLElement; + private _registered: boolean = false; + private _font: ITerminalFont; constructor( - private readonly _terminal: RawXtermTerminal, - private readonly _font: ITerminalFont, + private readonly _terminal: XtermTerminal, private readonly _capabilities: ITerminalCapabilityStore, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IModelService private readonly _modelService: IModelService, + @IConfigurationService configurationService: IConfigurationService ) { super(); - this.add(this._terminal.registerBufferElementProvider({ provideBufferElements: () => this.focus() })); + const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { + isSimpleWidget: true, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID]) + }; + this._font = this._terminal.getFont(); + const editorOptions: IEditorConstructionOptions = { + ...getSimpleEditorOptions(), + lineDecorationsWidth: 6, + dragAndDrop: true, + cursorWidth: 1, + fontSize: this._font.fontSize, + lineHeight: this._font.charHeight ? this._font.charHeight * this._font.lineHeight : 1, + fontFamily: this._font.fontFamily, + wrappingStrategy: 'advanced', + wrappingIndent: 'none', + padding: { top: 2, bottom: 2 }, + quickSuggestions: false, + scrollbar: { alwaysConsumeMouseWheel: false }, + renderWhitespace: 'none', + dropIntoEditor: { enabled: true }, + accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), + cursorBlinking: configurationService.getValue('terminal.integrated.cursorBlinking'), + readOnly: true + }; + this._accessibleBuffer = this._terminal.raw.element!.querySelector('.xterm-accessible-buffer') as HTMLElement; + this._editorContainer = document.createElement('div'); + this._bufferEditor = this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions); + this.add(configurationService.onDidChangeConfiguration(e => { + if (e.affectedKeys.has(TerminalSettingId.FontFamily)) { + this._font = this._terminal.getFont(); + } + })); } - focus(): DocumentFragment { - if (!this._bufferElementFragment) { - this._bufferElementFragment = document.createDocumentFragment(); - } - this._accessibleBuffer = this._terminal.element?.querySelector('.xterm-accessible-buffer') as HTMLElement || undefined; - if (!this._accessibleBuffer) { - return this._bufferElementFragment; - } - // see https://github.com/microsoft/vscode/issues/173532 - const accessibleBufferContentEditable = isLinux ? 'on' : this._configurationService.getValue(TerminalSettingId.AccessibleBufferContentEditable); - this._accessibleBuffer.contentEditable = accessibleBufferContentEditable === 'on' || (accessibleBufferContentEditable === 'auto' && !this._accessibilityService.isScreenReaderOptimized()) ? 'true' : 'false'; - // The viewport is undefined when this is focused, so we cannot get the cell height from that. Instead, estimate using the font. - const lineHeight = this._font?.charHeight ? this._font.charHeight * this._font.lineHeight + 'px' : ''; - this._accessibleBuffer.style.lineHeight = lineHeight; - const commands = this._capabilities.get(TerminalCapability.CommandDetection)?.commands; - if (!commands?.length) { - const noContent = document.createElement('div'); - const noContentLabel = localize('terminal.integrated.noContent', "No terminal content available for this session."); - noContent.textContent = noContentLabel; - this._bufferElementFragment.replaceChildren(noContent); - this._accessibleBuffer.focus(); - return this._bufferElementFragment; - } - let header; - let replaceChildren = true; - for (const command of commands) { - header = document.createElement('h2'); - // without this, the text area gets focused when keyboard shortcuts are used - header.tabIndex = -1; - header.textContent = command.command.replace(new RegExp(' ', 'g'), '\xA0'); - if (command.exitCode !== 0) { - header.textContent += ` exited with code ${command.exitCode}`; - } - const output = document.createElement('div'); - // without this, the text area gets focused when keyboard shortcuts are used - output.tabIndex = -1; - output.textContent = command.getOutput()?.replace(new RegExp(' ', 'g'), '\xA0') || ''; - if (replaceChildren) { - this._bufferElementFragment.replaceChildren(header, output); - replaceChildren = false; - } else { - this._bufferElementFragment.appendChild(header); - this._bufferElementFragment.appendChild(output); - } - } + async focus(): Promise { + await this._updateBufferEditor(); + // Updates xterm's accessibleBufferActive property + // such that mouse events do not cause the terminal buffer + // to steal the focus this._accessibleBuffer.focus(); - if (this._accessibleBuffer.contentEditable === 'true') { - document.execCommand('selectAll', false, undefined); - document.getSelection()?.collapseToEnd(); - } else if (header) { - // focus the cursor line's header - header.tabIndex = 0; + this._bufferEditor.focus(); + } + + private async _updateBufferEditor(): Promise { + if (!this._registered) { + // Registration is delayed until focus so the capability has time to have been added + this.add(this._terminal.raw.registerBufferElementProvider({ provideBufferElements: () => this._editorContainer })); + this._registered = true; } - return this._bufferElementFragment; + // When this is created, the element isn't yet attached so the dimensions are tiny + this._bufferEditor.layout({ width: this._accessibleBuffer.clientWidth, height: this._accessibleBuffer.clientHeight }); + const commandDetection = this._capabilities.has(TerminalCapability.CommandDetection); + const fragment = commandDetection ? this._getShellIntegrationContent() : this._getAllContent(); + const model = await this._getTextModel(URI.from({ scheme: ACCESSIBLE_BUFFER.Scheme, fragment })); + if (model) { + this._bufferEditor.setModel(model); + } + } + + async _getTextModel(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing && !existing.isDisposed()) { + return existing; + } + + return this._modelService.createModel(resource.fragment, null, resource, false); + } + + private _getShellIntegrationContent(): string { + const commands = this._capabilities.get(TerminalCapability.CommandDetection)?.commands; + const sb = new StringBuilder(10000); + let content = localize('terminal.integrated.noContent', "No terminal content available for this session. Run some commands to create content."); + if (!commands?.length) { + return content; + } + for (const command of commands) { + sb.appendString(command.command.replace(new RegExp(' ', 'g'), '\xA0')); + if (command.exitCode !== 0) { + sb.appendString(` exited with code ${command.exitCode}`); + } + sb.appendString('\n'); + sb.appendString(command.getOutput()?.replace(new RegExp(' ', 'g'), '\xA0') || ''); + } + content = sb.build(); + return content; + } + + private _getAllContent(): string { + const lines: string[] = []; + let currentLine: string = ''; + const buffer = this._terminal.raw.buffer.active; + const end = buffer.length; + for (let i = 0; i < end; i++) { + const line = buffer.getLine(i); + if (!line) { + continue; + } + const isWrapped = buffer.getLine(i + 1)?.isWrapped; + currentLine += line.translateToString(!isWrapped); + if (!isWrapped || i === end - 1) { + lines.push(currentLine.replace(new RegExp(' ', 'g'), '\xA0')); + currentLine = ''; + } + } + return lines.join('\n'); } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index fef77887bdb..9a889cabc54 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -253,7 +253,6 @@ export interface ITerminalConfiguration { }; useWslProfiles: boolean; altClickMovesCursor: boolean; - accessibleBufferContentEditable: 'auto' | 'on' | 'off'; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; gpuAcceleration: 'auto' | 'on' | 'canvas' | 'off'; diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index a2dc090c6db..a2fa3faf0fc 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -579,17 +579,6 @@ const terminalConfiguration: IConfigurationNode = { markdownDescription: localize('terminal.integrated.smoothScrolling', "Controls whether the terminal will scroll using an animation."), type: 'boolean', default: false - }, - [TerminalSettingId.AccessibleBufferContentEditable]: { - markdownDescription: localize('terminal.integrated.accessibleBufferContentEditable', "Controls whether the accessible buffer is marks as a `contenteditable` element. This adds a text cursor to the buffer, allowing selection with the keyboard without a screen reader. Screen reader users will typically want to leave this as `auto` or `off` which will treat the buffer similar to a document. By default, on Linux, this will be set to `on` so that it works when using Orca."), - type: 'string', - enum: ['auto', 'on', 'off'], - enumDescriptions: [ - localize('accessibleBufferContentEditable.auto', "Automatically enable when a screen reader is not detected."), - localize('accessibleBufferContentEditable.on', "Always on."), - localize('accessibleBufferContentEditable.off', "Always off.") - ], - default: 'auto' } } }; diff --git a/yarn.lock b/yarn.lock index 55375683415..2b3fa64c4ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11863,15 +11863,15 @@ xterm-addon-webgl@0.15.0-beta.7: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.7.tgz#ab247b499f61e8eebff92e08ec5ca999d87e06af" integrity sha512-7WCI/D6uFNp3y9TeTsbSo1h7gCy4h/yP2lWn8ZEjCaiGvO11DbKMq17fbiwaR3YmGWXoRKkcLaNIiqxFnjKO4w== -xterm-headless@5.2.0-beta.28: - version "5.2.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.28.tgz#d2c149da51ef138f46268b755c4fdc4202eb771c" - integrity sha512-4XcjBhFwuyjpz2ubESwp75UceySOOKdJszKyyxOQ3/7L937uiVEBBLc8T231XU8lSwWUU7czyNjYyCfpszY4+Q== +xterm-headless@5.2.0-beta.29: + version "5.2.0-beta.29" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.29.tgz#dd08312fdb4292c217e685d9e2e8b1957364e298" + integrity sha512-1P4urIeDTkl2C+zGb4WUnKJMACZMPGYHwVXMjkB0WhMISbkt6M34MH9ljxHhnL99dHwlx2Lvi6wvhnpyZucWCg== -xterm@5.2.0-beta.28: - version "5.2.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.28.tgz#852347e4eaf5aae7d82c90592a42adc9daab2a79" - integrity sha512-aLDxCuqjWHjvnhfWfkxy/y6coNrC+QIhbDe2sdfLPkrxhK6KnYE6qiZD5jXUIQXeq0KmSDcYi/esuKujvobC2A== +xterm@5.2.0-beta.29: + version "5.2.0-beta.29" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.29.tgz#99764aff5cd8cdb4335f5d59466b134cfcb45e3e" + integrity sha512-zx5RKcQqo78bza4R/m3WtxAJCBAF4U61fy6cxqb1PkqXF9/qdYlySUCVOauMxv+6n6cAxt3EQWwLlgvbvQBbsw== y18n@^3.2.1: version "3.2.2" From 668f3de5b4b2018cd78db74ece6277276fb80c6c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 16 Feb 2023 18:27:43 +0100 Subject: [PATCH 66/85] fix #173819 (#174602) --- .../userDataSync/test/common/userDataAutoSyncService.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index db82505fc6d..7f6e72d2ff2 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -381,7 +382,7 @@ suite('UserDataAutoSyncService', () => { const errorPromise = Event.toPromise(testObject.onError); await testObject.sync(); - const e = await errorPromise; + const e = await Promise.race([errorPromise, timeout(0)]); assert.ok(e instanceof UserDataAutoSyncError); assert.deepStrictEqual((e).code, UserDataSyncErrorCode.SessionExpired); assert.deepStrictEqual(target.requests, [ From 5c8adac31c75dbebab86255a3889bd2bf8fd9b5a Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:02:44 -0800 Subject: [PATCH 67/85] Notebook search: flag should ask for re-search (#174500) Fixes #174493 --- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 652eb800920..8f0b863a10a 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -352,7 +352,7 @@ configurationRegistry.registerConfiguration({ }, 'search.experimental.notebookSearch': { type: 'boolean', - description: nls.localize('search.experimental.notebookSearch', "Controls whether to use the experimental notebook search in the global search."), + description: nls.localize('search.experimental.notebookSearch', "Controls whether to use the experimental notebook search in the global search. Please refresh your search for changes to this setting to take effect."), default: false }, } From 5cfc582b17945eae2bae29f7048f605f565f9236 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 16 Feb 2023 11:22:29 -0700 Subject: [PATCH 68/85] Fix #174024 (#174607) --- src/vs/workbench/contrib/timeline/browser/timelinePane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index bb387304c73..e1cd80f4e1a 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -504,7 +504,7 @@ export class TimelinePane extends ViewPane { this.pendingRequests.clear(); - if (!this.isBodyVisible()) { + if (!this.isBodyVisible() && this.tree) { this.tree.setChildren(null, undefined); this._isEmpty = true; } From c6a15221486618b9c85d7fe5a59110b720175818 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 16 Feb 2023 10:41:10 -0800 Subject: [PATCH 69/85] update issues milestone. (#174609) --- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 2 +- .vscode/notebooks/verification.github-issues | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index d3b3ae08fa5..4a61518494e 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"January 2023\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"February 2023\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index f1c838cdeb7..57e276cb7f2 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"January 2023\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"February 2023\"\n\n$MINE=assignee:@me" }, { "kind": 1, diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index b4ad043d9bf..15cf60b17dd 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n$milestone=milestone:\"November 2022\"" + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n$milestone=milestone:\"February 2023\"" }, { "kind": 1, From 13b161f9f2373991be06614c8530b38ca2e63287 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 16 Feb 2023 11:35:10 -0800 Subject: [PATCH 70/85] Fix #174551. There might be a race between editor and contrib dispose. (#174611) --- .../notebook/browser/contrib/outline/notebookOutline.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 68769f72685..6e3b8b2c1ad 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -531,6 +531,10 @@ export class NotebookCellOutline implements IOutline { const markerServiceListener = new MutableDisposable(); this._entriesDisposables.add(markerServiceListener); const updateMarkerUpdater = () => { + if (notebookEditorWidget.isDisposed) { + return; + } + const doUpdateMarker = (clear: boolean) => { for (const entry of this._entries) { if (clear) { From 6b45fd4bffa8f9bf918ce1bd80a17649078c466a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 16 Feb 2023 11:36:12 -0800 Subject: [PATCH 71/85] Polish default line wrap in text/error output (#174615) --- extensions/notebook-renderers/src/index.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 9220ff0c79f..8648045a8c1 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -260,21 +260,16 @@ export const activate: ActivationFunction = (ctx) => { -webkit-user-select: text; -ms-user-select: text; cursor: auto; + word-wrap: break-word; + /* text/stream output container should scroll but preserve newline character */ + white-space: pre; } + /* When wordwrap turned on, force it to pre-wrap */ .output-plaintext.wordWrap span, .output-stream.wordWrap span, .traceback.wordWrap span { - white-space: pre-wrap !important; - word-break: break-all; - } - .output-plaintext, - .output-stream { white-space: pre-wrap; } - output-plaintext, - .traceback { - word-wrap: break-word; - } .output > .scrollable { overflow-y: scroll; max-height: var(--notebook-cell-output-max-height); From c7a040ae701f905ee74744dc85b0f26eb3a7df36 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 16 Feb 2023 14:02:31 -0600 Subject: [PATCH 72/85] use correct `title` type so `Toggle Tab Moves Focus Mode` shows up in the command palette (#174610) fix #174450 --- .../contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts b/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts index 6feef76d443..6d9f2a8c6a1 100644 --- a/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts +++ b/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts @@ -18,13 +18,14 @@ export class ToggleTabFocusModeAction extends Action2 { constructor() { super({ id: ToggleTabFocusModeAction.ID, - title: nls.localize({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, "Toggle Tab Key Moves Focus"), + title: { value: nls.localize({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, 'Toggle Tab Key Moves Focus'), original: 'Toggle Tab Key Moves Focus' }, precondition: undefined, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyM, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KeyM }, weight: KeybindingWeight.EditorContrib - } + }, + f1: true }); } From 56ba527736cc5c2ccc5073464923990840ed1049 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 16 Feb 2023 16:03:55 -0600 Subject: [PATCH 73/85] add audio cue for when a terminal command fails (#174621) --- src/vs/platform/audioCues/browser/audioCueService.ts | 6 ++++++ .../audioCues/browser/audioCues.contribution.ts | 4 ++++ .../contrib/terminal/browser/xterm/decorationAddon.ts | 11 +++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index fa212de0883..8f4a1384e58 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -294,6 +294,12 @@ export class AudioCue { settingsKey: 'audioCues.taskFailed' }); + public static readonly terminalCommandFailed = AudioCue.register({ + name: localize('audioCues.terminalCommandFailed', 'Terminal Command Failed'), + sound: Sound.taskFailed, + settingsKey: 'audioCues.terminalCommandFailed' + }); + public static readonly terminalBell = AudioCue.register({ name: localize('audioCues.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 1f345057700..61153542d54 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -89,6 +89,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), ...audioCueFeatureBase, }, + 'audioCues.terminalCommandFailed': { + 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase, + }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), ...audioCueFeatureBase, diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index bb8d82f88ab..4c9e394aa06 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -26,6 +26,7 @@ import { ITerminalCommand } from 'vs/workbench/contrib/terminal/common/terminal' import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_SUCCESS_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IDecoration, ITerminalAddon, Terminal } from 'xterm'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } @@ -51,7 +52,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @IQuickInputService private readonly _quickInputService: IQuickInputService, @ILifecycleService lifecycleService: ILifecycleService, @ICommandService private readonly _commandService: ICommandService, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @IAudioCueService private readonly _audioCueService: IAudioCueService ) { super(); this._register(toDisposable(() => this._dispose())); @@ -217,7 +219,12 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { for (const command of capability.commands) { this.registerCommandDecoration(command); } - commandDetectionListeners.push(capability.onCommandFinished(command => this.registerCommandDecoration(command))); + commandDetectionListeners.push(capability.onCommandFinished(command => { + this.registerCommandDecoration(command); + if (command.exitCode) { + this._audioCueService.playAudioCue(AudioCue.terminalCommandFailed); + } + })); // Command invalidated commandDetectionListeners.push(capability.onCommandInvalidated(commands => { for (const command of commands) { From 1ccf76379c105789095f3eb0868c92ffc90d48e3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 16 Feb 2023 15:40:45 -0700 Subject: [PATCH 74/85] Use a better context view in explorer hover (#174626) Use a better context view --- .../files/browser/media/explorerviewlet.css | 1 + .../files/browser/views/explorerViewer.ts | 17 +++++++++-------- .../workbench/services/hover/browser/hover.ts | 5 ----- .../services/hover/browser/hoverService.ts | 5 ----- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index ad60aa1aba4..fb6d440dfaa 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -15,6 +15,7 @@ padding-left: 6px; height: 22px; font-size: 13px !important; + user-select: none !important; } .explorer-folders-view .monaco-list-row { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 30a647243bb..c515fd7ed6a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -281,6 +281,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - this.hoverService.hideHover(); - element.dispatchEvent(new MouseEvent(e.type, { ...e, bubbles: true })); - }, hoverPosition: HoverPosition.RIGHT, }, focus) : undefined; } onDidHideHover(): void { - this.lastHoverHideTime = Date.now(); + if (!this.hiddenFromClick) { + this.lastHoverHideTime = Date.now(); + } + this.hiddenFromClick = false; } }(this.configurationService, this.hoverService); diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index cd428efb0a4..1f1de94425a 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -134,11 +134,6 @@ export interface IHoverOptions { */ trapFocus?: boolean; - /** - * A callback which will be executed when the hover is clicked - */ - onClick?(e: MouseEvent): void; - /* * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover * in. This is particularly useful for more natural tab focusing behavior, where the hover is diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index a6cd997618e..47127a62549 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -64,11 +64,6 @@ export class HoverService implements IHoverService { options.container ); hover.onRequestLayout(() => provider.layout()); - if (options.onClick) { - hoverDisposables.add(addDisposableListener(hover.domNode, EventType.CLICK, e => { - options.onClick!(e); - })); - } if ('targetElements' in options.target) { for (const element of options.target.targetElements) { hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover())); From 773668e936311f7945240aad84072dc6f60c5d6c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 16 Feb 2023 16:41:32 -0600 Subject: [PATCH 75/85] add settings for tab moves focus (#174265) --- .../browser/config/editorConfiguration.ts | 1 + src/vs/editor/common/config/editorOptions.ts | 25 ++++------- src/vs/monaco.d.ts | 4 ++ src/vs/platform/terminal/common/terminal.ts | 1 + .../browser/parts/editor/editorStatus.ts | 2 +- .../terminal/browser/terminalInstance.ts | 1 - .../browser/terminalMainContribution.ts | 41 +++++++++++++++++-- .../terminal/common/terminalConfiguration.ts | 5 +++ 8 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index e3f41e0d416..752adff3c47 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -86,6 +86,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat this._register(FontMeasurements.onDidChange(() => this._recomputeOptions())); this._register(browser.PixelRatio.onDidChange(() => this._recomputeOptions())); this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions())); + TabFocus.setTabFocusMode(this.options.get(EditorOption.tabFocusMode)); } private _recomputeOptions(): void { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 1de055a7f5c..3383600a83b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -698,6 +698,11 @@ export interface IEditorOptions { * When enabled, this shows a preview of the drop location and triggers an `onDropIntoEditor` event. */ dropIntoEditor?: IDropIntoEditorOptions; + + /** + * Controls whether the editor receives tabs or defers them to the workbench for navigation. + */ + tabFocusMode?: boolean; } /** @@ -4569,22 +4574,6 @@ class SmartSelect extends BaseEditorOption { - - constructor() { - super(EditorOption.tabFocusMode); - } - - public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: boolean): boolean { - const readOnly = options.get(EditorOption.readOnly); - return (readOnly ? true : env.tabFocusMode); - } -} - -//#endregion - //#region wrappingIndent /** @@ -5619,7 +5608,9 @@ export const EditorOptions = { // Leave these at the end (because they have dependencies!) editorClassName: register(new EditorClassName()), pixelRatio: register(new EditorPixelRatio()), - tabFocusMode: register(new EditorTabFocusMode()), + tabFocusMode: register(new EditorBooleanOption(EditorOption.tabFocusMode, 'tabFocusMode', false, + { markdownDescription: nls.localize('tabFocusMode', "Controls whether the editor receives tabs or defers them to the workbench for navigation.") } + )), layoutInfo: register(new EditorLayoutInfoComputer()), wrappingInfo: register(new EditorWrappingInfoComputer()), wrappingIndent: register(new WrappingIndentOption()), diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 27d0f7945ec..ea87b12a731 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3666,6 +3666,10 @@ declare namespace monaco.editor { * When enabled, this shows a preview of the drop location and triggers an `onDropIntoEditor` event. */ dropIntoEditor?: IDropIntoEditorOptions; + /** + * Controls whether the editor receives tabs or defers them to the workbench for navigation. + */ + tabFocusMode?: boolean; } export interface IDiffEditorBaseOptions { diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 56496a8deff..4877f38c2b7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -84,6 +84,7 @@ export const enum TerminalSettingId { CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell', AllowChords = 'terminal.integrated.allowChords', AllowMnemonics = 'terminal.integrated.allowMnemonics', + TabFocusMode = 'terminal.integrated.tabFocusMode', EnvMacOs = 'terminal.integrated.env.osx', EnvLinux = 'terminal.integrated.env.linux', EnvWindows = 'terminal.integrated.env.windows', diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 746dadb68a5..551dde7791f 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -318,7 +318,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); this._register(this.textFileService.untitled.onDidChangeEncoding(model => this.onResourceEncodingChange(model.resource))); this._register(this.textFileService.files.onDidChangeEncoding(model => this.onResourceEncodingChange((model.resource)))); - this._register(TabFocus.onDidChangeTabFocus(() => this.onTabFocusModeChange())); + this._register(Event.runAndSubscribe(TabFocus.onDidChangeTabFocus, () => this.onTabFocusModeChange())); } private registerCommands(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2fa0b772354..8b1f31a4e41 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -473,7 +473,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } return findWidget; }); - this._logService.trace(`terminalInstance#ctor (instanceId: ${this.instanceId})`, this._shellLaunchConfig); this._register(this.capabilities.onDidAddCapability(e => { this._logService.debug('terminalInstance added capability', e); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts b/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts index 9e722549503..46a75e59421 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts @@ -5,8 +5,11 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TabFocus } from 'vs/editor/browser/config/tabFocus'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ILabelService } from 'vs/platform/label/common/label'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITerminalEditorService, ITerminalGroupService, ITerminalService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; @@ -18,13 +21,16 @@ import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/s * be more relevant). */ export class TerminalMainContribution extends Disposable implements IWorkbenchContribution { + private _editorTabFocus: boolean | undefined; + private _terminalTabFocus: boolean | undefined; constructor( @IEditorResolverService editorResolverService: IEditorResolverService, - @IEnvironmentService environmentService: IEnvironmentService, @ILabelService labelService: ILabelService, @ITerminalService terminalService: ITerminalService, @ITerminalEditorService terminalEditorService: ITerminalEditorService, - @ITerminalGroupService terminalGroupService: ITerminalGroupService + @ITerminalGroupService terminalGroupService: ITerminalGroupService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); @@ -70,6 +76,33 @@ export class TerminalMainContribution extends Disposable implements IWorkbenchCo separator: '' } }); - } + const viewKey = new Set(); + viewKey.add('focusedView'); + TabFocus.onDidChangeTabFocus(tabFocus => { + if (contextKeyService.getContextKeyValue('focusedView') === 'terminal') { + this._terminalTabFocus = tabFocus; + } else { + this._editorTabFocus = tabFocus; + } + }); + this._register(contextKeyService.onDidChangeContext((c) => { + if (c.affectsSome(viewKey)) { + if (contextKeyService.getContextKeyValue('focusedView') === 'terminal') { + this._editorTabFocus = TabFocus.getTabFocusMode(); + TabFocus.setTabFocusMode(this._terminalTabFocus ?? configurationService.getValue(TerminalSettingId.TabFocusMode)); + } else { + this._terminalTabFocus = TabFocus.getTabFocusMode(); + TabFocus.setTabFocusMode(this._editorTabFocus ?? configurationService.getValue('editor.tabFocusMode')); + } + } + })); + this._register(configurationService.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('editor.tabFocusMode')) { + this._editorTabFocus = undefined; + } else if (e.affectsConfiguration(TerminalSettingId.TabFocusMode)) { + this._terminalTabFocus = undefined; + } + })); + } } diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index a2fa3faf0fc..e07eeb45570 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -106,6 +106,11 @@ const terminalConfiguration: IConfigurationNode = { default: 'right', description: localize('terminal.integrated.tabs.location', "Controls the location of the terminal tabs, either to the left or right of the actual terminal(s).") }, + [TerminalSettingId.TabFocusMode]: { + markdownDescription: localize('tabFocusMode', "Controls whether the terminal receives tabs or defers them to the workbench for navigation. this overrides {0} when the terminal is focused.", '`#editor.tabFocusMode#`'), + type: 'boolean', + default: false + }, [TerminalSettingId.DefaultLocation]: { type: 'string', enum: [TerminalLocationString.Editor, TerminalLocationString.TerminalView], From a1665b75bf716cdb9db7ae3dfba678b70fe3a77b Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 16 Feb 2023 15:56:32 -0800 Subject: [PATCH 76/85] put open new editor link outside of scrollable div --- extensions/notebook-renderers/src/index.ts | 2 +- extensions/notebook-renderers/src/textHelper.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 8648045a8c1..f0f28feb2fb 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -270,7 +270,7 @@ export const activate: ActivationFunction = (ctx) => { .traceback.wordWrap span { white-space: pre-wrap; } - .output > .scrollable { + .output .scrollable { overflow-y: scroll; max-height: var(--notebook-cell-output-max-height); border: var(--vscode-editorWidget-border); diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index 3555307ec9d..560aec613e0 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -50,14 +50,14 @@ function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number } function scrollableArrayOfString(id: string, buffer: string[], container: HTMLElement, trustHtml: boolean) { - container.classList.add('scrollable'); + const scrollableDiv = document.createElement('div'); + scrollableDiv.classList.add('scrollable'); if (buffer.length > 5000) { container.appendChild(generateViewMoreElement(id, false)); } - const div = document.createElement('div'); - container.appendChild(div); - div.appendChild(handleANSIOutput(buffer.slice(0, 5000).join('\n'), trustHtml)); + container.appendChild(scrollableDiv); + scrollableDiv.appendChild(handleANSIOutput(buffer.slice(0, 5000).join('\n'), trustHtml)); } export function insertOutput(id: string, outputs: string[], linesLimit: number, scrollable: boolean, container: HTMLElement, trustHtml: boolean) { From a18d7bc586adac42fe49a0c15878fb5e04783b94 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 16 Feb 2023 16:27:00 -0800 Subject: [PATCH 77/85] Fix #174552. NPE (#174630) * Fix #174552. NPE * Fix #174555. --- .../contrib/notebook/browser/notebook.contribution.ts | 4 ++++ .../contrib/notebook/browser/viewModel/baseCellViewModel.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 6ea12228fbc..a461f1e77dc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -303,6 +303,10 @@ class CellContentProvider implements ITextModelContentProvider { const ref = await this._notebookModelResolverService.resolve(data.notebook); let result: ITextModel | null = null; + if (!ref.object.isResolved()) { + return null; + } + for (const cell of ref.object.notebook.cells) { if (cell.uri.toString() === resource.toString()) { const bufferFactory: ITextBufferFactory = { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 284753928a7..3ca7b327dbc 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -256,7 +256,7 @@ export abstract class BaseCellViewModel extends Disposable { writeTransientState(editor.getModel(), this._editorTransientState, this._codeEditorService); } - this._textEditor.changeDecorations((accessor) => { + this._textEditor?.changeDecorations((accessor) => { this._resolvedDecorations.forEach((value, key) => { if (key.startsWith('_lazy_')) { // lazy ones From 71f619cbda2b051d0b083a6393b175abd1ef30ad Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 16 Feb 2023 18:34:39 -0800 Subject: [PATCH 78/85] Ensure the original scopes requested are the scopes in the Session returned (#174645) Fixes #174631 Fixes #167791 --- .../microsoft-authentication/src/AADHelper.ts | 71 +++++++++---------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index a6126dd7084..c49c708e14c 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -62,6 +62,7 @@ export interface IMicrosoftTokens { } interface IScopeData { + originalScopes?: string[]; scopes: string[]; scopeStr: string; scopesToSend: string; @@ -210,27 +211,28 @@ export class AzureActiveDirectoryService { } } + const clientId = this.getClientId(scopes); + const scopeData: IScopeData = { + clientId, + originalScopes: scopes, + scopes: modifiedScopes, + scopeStr: modifiedScopesStr, + // filter our special scopes + scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), + tenant: this.getTenantId(scopes), + }; + // If we still don't have a matching token try to get a new token from an existing token by using // the refreshToken. This is documented here: // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token // "Refresh tokens are valid for all permissions that your client has already received consent for." if (!matchingTokens.length) { - const clientId = this.getClientId(modifiedScopes); // Get a token with the correct client id. const token = clientId === DEFAULT_CLIENT_ID ? this._tokens.find(t => t.refreshToken && !t.scope.includes('VSCODE_CLIENT_ID')) : this._tokens.find(t => t.refreshToken && t.scope.includes(`VSCODE_CLIENT_ID:${clientId}`)); if (token) { - const scopeData: IScopeData = { - clientId, - scopes: modifiedScopes, - scopeStr: modifiedScopesStr, - // filter our special scopes - scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - tenant: this.getTenantId(modifiedScopes), - }; - try { const itoken = await this.refreshToken(token.refreshToken, scopeData); matchingTokens.push(itoken); @@ -241,36 +243,35 @@ export class AzureActiveDirectoryService { } Logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`); - return Promise.all(matchingTokens.map(token => this.convertToSession(token))); + return Promise.all(matchingTokens.map(token => this.convertToSession(token, scopeData))); } public createSession(scopes: string[]): Promise { - if (!scopes.includes('openid')) { - scopes.push('openid'); + let modifiedScopes = [...scopes]; + if (!modifiedScopes.includes('openid')) { + modifiedScopes.push('openid'); } - if (!scopes.includes('email')) { - scopes.push('email'); + if (!modifiedScopes.includes('email')) { + modifiedScopes.push('email'); } - if (!scopes.includes('profile')) { - scopes.push('profile'); + if (!modifiedScopes.includes('profile')) { + modifiedScopes.push('profile'); } - if (!scopes.includes('offline_access')) { - scopes.push('offline_access'); + if (!modifiedScopes.includes('offline_access')) { + modifiedScopes.push('offline_access'); } - scopes = scopes.sort(); + modifiedScopes = modifiedScopes.sort(); const scopeData: IScopeData = { - scopes, - scopeStr: scopes.join(' '), + originalScopes: scopes, + scopes: modifiedScopes, + scopeStr: modifiedScopes.join(' '), // filter our special scopes - scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '), + scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), clientId: this.getClientId(scopes), tenant: this.getTenantId(scopes), }; Logger.info(`Logging in for the following scopes: ${scopeData.scopeStr}`); - if (!scopeData.scopes.includes('offline_access')) { - Logger.info('Warning: The \'offline_access\' scope was not included, so the generated token will not be able to be refreshed.'); - } const runsRemote = vscode.env.remoteName !== undefined; const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web; @@ -511,7 +512,7 @@ export class AzureActiveDirectoryService { }; } - private async convertToSession(token: IToken): Promise { + private async convertToSession(token: IToken, scopeData: IScopeData): Promise { if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { token.expiresAt ? Logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`) @@ -521,21 +522,12 @@ export class AzureActiveDirectoryService { accessToken: token.accessToken, idToken: token.idToken, account: token.account, - scopes: token.scope.split(' ') + scopes: scopeData.originalScopes ?? scopeData.scopes }; } try { Logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`); - const scopes = token.scope.split(' '); - const scopeData: IScopeData = { - scopes, - scopeStr: token.scope, - // filter our special scopes - scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - clientId: this.getClientId(scopes), - tenant: this.getTenantId(scopes), - }; const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId); if (refreshedToken.accessToken) { return { @@ -543,7 +535,8 @@ export class AzureActiveDirectoryService { accessToken: refreshedToken.accessToken, idToken: refreshedToken.idToken, account: token.account, - scopes: token.scope.split(' ') + // We always prefer the original scopes requested since that array is used as a key in the AuthService + scopes: scopeData.originalScopes ?? scopeData.scopes }; } else { throw new Error(); @@ -729,7 +722,7 @@ export class AzureActiveDirectoryService { } this.setToken(token, scopeData); Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); - return await this.convertToSession(token); + return await this.convertToSession(token, scopeData); } private async fetchTokenResponse(endpoint: string, postData: string, scopeData: IScopeData): Promise { From 31edbf7ca6bd8d27a0e2e35dc5a1690b196a16e2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 17 Feb 2023 10:18:01 +0100 Subject: [PATCH 79/85] sandbox - move shared process to `node` layer (#174581) --- build/gulpfile.vscode.js | 2 +- src/buildfile.js | 6 +-- src/cli.js | 2 +- src/vs/code/node/{ => cli}/cliProcessMain.ts | 0 src/vs/code/node/{cli.ts => cli/main.ts} | 0 .../sharedProcess/contrib/codeCacheCleaner.ts | 0 .../sharedProcess/contrib/extensions.ts | 0 .../contrib/languagePackCachedDataCleaner.ts | 0 .../contrib/localizationsUpdater.ts | 0 .../sharedProcess/contrib/logsDataCleaner.ts | 0 .../contrib/storageDataCleaner.ts | 7 ++- .../contrib/userDataProfilesCleaner.ts | 0 .../sharedProcess/sharedProcess-dev.html | 0 .../sharedProcess/sharedProcess.html | 0 .../sharedProcess/sharedProcess.js | 2 +- .../sharedProcess/sharedProcessMain.ts | 49 ++++++++++--------- .../electron-main/sharedProcess.ts | 4 +- 17 files changed, 39 insertions(+), 33 deletions(-) rename src/vs/code/node/{ => cli}/cliProcessMain.ts (100%) rename src/vs/code/node/{cli.ts => cli/main.ts} (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/codeCacheCleaner.ts (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/extensions.ts (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/languagePackCachedDataCleaner.ts (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/localizationsUpdater.ts (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/logsDataCleaner.ts (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/storageDataCleaner.ts (96%) rename src/vs/code/{electron-browser => node}/sharedProcess/contrib/userDataProfilesCleaner.ts (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/sharedProcess-dev.html (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/sharedProcess.html (100%) rename src/vs/code/{electron-browser => node}/sharedProcess/sharedProcess.js (91%) rename src/vs/code/{electron-browser => node}/sharedProcess/sharedProcessMain.ts (97%) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index e96ab92ecac..938943d7809 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -126,7 +126,7 @@ const optimizeVSCodeTask = task.define('optimize-vscode', task.series( { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-sandbox/workbench/workbench.js'], out: 'vs/code/electron-sandbox/workbench/workbench.js' }, { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-sandbox/issue/issueReporter.js'], out: 'vs/code/electron-sandbox/issue/issueReporter.js' }, { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js'], out: 'vs/code/electron-sandbox/processExplorer/processExplorer.js' }, - { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js'], out: 'vs/code/electron-browser/sharedProcess/sharedProcess.js' } + { src: [...windowBootstrapFiles, 'out-build/vs/code/node/sharedProcess/sharedProcess.js'], out: 'vs/code/node/sharedProcess/sharedProcess.js' } ] } ) diff --git a/src/buildfile.js b/src/buildfile.js index de976f51000..d64321e3ffd 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -73,10 +73,10 @@ exports.keyboardMaps = [ exports.code = [ createModuleDescription('vs/code/electron-main/main'), - createModuleDescription('vs/code/node/cli'), - createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), + createModuleDescription('vs/code/node/cli/main'), + createModuleDescription('vs/code/node/cli/cliProcessMain', ['vs/code/node/cli/main']), createModuleDescription('vs/code/electron-sandbox/issue/issueReporterMain'), - createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain'), + createModuleDescription('vs/code/node/sharedProcess/sharedProcessMain'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain') ]; diff --git a/src/cli.js b/src/cli.js index dd5ea5351ec..2166ce262d5 100644 --- a/src/cli.js +++ b/src/cli.js @@ -28,4 +28,4 @@ bootstrap.enableASARSupport(); process.env['VSCODE_CLI'] = '1'; // Load CLI through AMD loader -require('./bootstrap-amd').load('vs/code/node/cli'); +require('./bootstrap-amd').load('vs/code/node/cli/main'); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cli/cliProcessMain.ts similarity index 100% rename from src/vs/code/node/cliProcessMain.ts rename to src/vs/code/node/cli/cliProcessMain.ts diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli/main.ts similarity index 100% rename from src/vs/code/node/cli.ts rename to src/vs/code/node/cli/main.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner.ts b/src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensions.ts b/src/vs/code/node/sharedProcess/contrib/extensions.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/extensions.ts rename to src/vs/code/node/sharedProcess/contrib/extensions.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts b/src/vs/code/node/sharedProcess/contrib/localizationsUpdater.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts rename to src/vs/code/node/sharedProcess/contrib/localizationsUpdater.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/logsDataCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/logsDataCleaner.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts similarity index 96% rename from src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts index 1f55a52057a..bdc3d6a6b50 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts @@ -9,13 +9,16 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { join } from 'vs/base/common/path'; import { Promises } from 'vs/base/node/pfs'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { StorageClient } from 'vs/platform/storage/common/storageIpc'; import { EXTENSION_DEVELOPMENT_EMPTY_WINDOW_WORKSPACE } from 'vs/platform/workspace/common/workspace'; import { NON_EMPTY_WORKSPACE_ID_LENGTH } from 'vs/platform/workspaces/node/workspaces'; +/* eslint-disable local/code-layering, local/code-import-patterns */ +// TODO@bpasero layer is not allowed in utility process +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; + export class UnusedWorkspaceStorageDataCleaner extends Disposable { constructor( diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/userDataProfilesCleaner.ts b/src/vs/code/node/sharedProcess/contrib/userDataProfilesCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/userDataProfilesCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/userDataProfilesCleaner.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess-dev.html b/src/vs/code/node/sharedProcess/sharedProcess-dev.html similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/sharedProcess-dev.html rename to src/vs/code/node/sharedProcess/sharedProcess-dev.html diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.html b/src/vs/code/node/sharedProcess/sharedProcess.html similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/sharedProcess.html rename to src/vs/code/node/sharedProcess/sharedProcess.html diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/node/sharedProcess/sharedProcess.js similarity index 91% rename from src/vs/code/electron-browser/sharedProcess/sharedProcess.js rename to src/vs/code/node/sharedProcess/sharedProcess.js index 1383bc8331e..dfb0cefbede 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/node/sharedProcess/sharedProcess.js @@ -10,7 +10,7 @@ const bootstrapWindow = bootstrapWindowLib(); // Load shared process into window - bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { + bootstrapWindow.load(['vs/code/node/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { return sharedProcess.main(configuration); }, { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts similarity index 97% rename from src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts rename to src/vs/code/node/sharedProcess/sharedProcessMain.ts index 486db15af16..dd9c462b46f 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -11,13 +11,12 @@ import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import { Server as BrowserWindowMessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; import { Server as UtilityProcessMessagePortServer, once } from 'vs/base/parts/ipc/node/ipc.mp'; -import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner'; -import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; -import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater'; -import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; -import { UnusedWorkspaceStorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; +import { CodeCacheCleaner } from 'vs/code/node/sharedProcess/contrib/codeCacheCleaner'; +import { LanguagePackCachedDataCleaner } from 'vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner'; +import { LocalizationsUpdater } from 'vs/code/node/sharedProcess/contrib/localizationsUpdater'; +import { LogsDataCleaner } from 'vs/code/node/sharedProcess/contrib/logsDataCleaner'; +import { UnusedWorkspaceStorageDataCleaner } from 'vs/code/node/sharedProcess/contrib/storageDataCleaner'; import { IChecksumService } from 'vs/platform/checksum/common/checksumService'; import { ChecksumService } from 'vs/platform/checksum/node/checksumService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -33,10 +32,8 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/ import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService'; import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; -import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -44,19 +41,15 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { ConsoleLogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; @@ -65,7 +58,6 @@ import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService' import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; @@ -79,8 +71,6 @@ import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/u import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; -import { UserDataProfileStorageService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -90,7 +80,6 @@ import { SharedTunnelsService } from 'vs/platform/tunnel/node/tunnelService'; import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService'; import { SharedProcessTunnelService } from 'vs/platform/tunnel/node/sharedProcessTunnelService'; import { ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService'; -import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { isLinux } from 'vs/base/common/platform'; @@ -105,20 +94,34 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; -import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -import { UserDataProfilesCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/userDataProfilesCleaner'; -import { RemoteTunnelService } from 'vs/platform/remoteTunnel/electron-browser/remoteTunnelService'; +import { UserDataProfilesCleaner } from 'vs/code/node/sharedProcess/contrib/userDataProfilesCleaner'; import { IRemoteTunnelService } from 'vs/platform/remoteTunnel/common/remoteTunnel'; -import { ISharedProcessLifecycleService, SharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService'; import { UserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSyncResourceProvider'; -import { ExtensionsContributions } from 'vs/code/electron-browser/sharedProcess/contrib/extensions'; -import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService'; +import { ExtensionsContributions } from 'vs/code/node/sharedProcess/contrib/extensions'; import { localize } from 'vs/nls'; import { LogService } from 'vs/platform/log/common/logService'; import { ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerConfiguration } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService'; import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes'; +/* eslint-disable local/code-layering, local/code-import-patterns */ +// TODO@bpasero layer is not allowed in utility process +import { Server as BrowserWindowMessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; +import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; +import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; +import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; +import { UserDataProfileStorageService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService'; +import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService'; +import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService'; +import { RemoteTunnelService } from 'vs/platform/remoteTunnel/electron-browser/remoteTunnelService'; +import { ISharedProcessLifecycleService, SharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService'; +import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService'; + class SharedProcessMain extends Disposable { private readonly server: IPCServer; @@ -475,7 +478,7 @@ class SharedProcessMain extends Disposable { process.on('uncaughtException', error => onUnexpectedError(error)); process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); } else { - window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { + (globalThis as any).addEventListener('unhandledrejection', (event: any) => { // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent onUnexpectedError(event.reason); diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index efecede2fea..0fc12536e0c 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -281,7 +281,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { this.utilityProcess.start({ type: 'shared-process', - entryPoint: 'vs/code/electron-browser/sharedProcess/sharedProcessMain', + entryPoint: 'vs/code/node/sharedProcess/sharedProcessMain', payload: this.createSharedProcessConfiguration(), execArgv }); @@ -312,7 +312,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { configObjectUrl.update(this.createSharedProcessConfiguration()); // Load with config - this.window.loadURL(FileAccess.asBrowserUri(`vs/code/electron-browser/sharedProcess/sharedProcess${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); + this.window.loadURL(FileAccess.asBrowserUri(`vs/code/node/sharedProcess/sharedProcess${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); } private createSharedProcessConfiguration(): ISharedProcessConfiguration { From 578201ff7865263a8709d12cc5f51057b32babb9 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 17 Feb 2023 11:22:31 +0100 Subject: [PATCH 80/85] Don't store/restore ports in an empty workspace (#174663) * Don't store/restore ports in an empty workspace * warn -> debug --- .../remote/common/remoteExplorerService.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 159961dd545..07de8951347 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -519,9 +519,13 @@ export class TunnelModel extends Disposable { return URI.parse(`${protocol}://${localAddress}`); } - private async getStorageKey(): Promise { + private async getStorageKey(): Promise { const workspace = this.workspaceContextService.getWorkspace(); const workspaceHash = workspace.configuration ? hash(workspace.configuration.path) : (workspace.folders.length > 0 ? hash(workspace.folders[0].uri.path) : undefined); + if (workspaceHash === undefined) { + this.logService.debug('Could not get workspace hash for forwarded ports storage key.'); + return undefined; + } return `${TUNNELS_TO_RESTORE}.${this.environmentService.remoteAuthority}.${workspaceHash}`; } @@ -532,8 +536,11 @@ export class TunnelModel extends Disposable { await this.storeForwarded(); return deprecatedValue; } - - return this.storageService.get(await this.getStorageKey(), StorageScope.PROFILE); + const storageKey = await this.getStorageKey(); + if (!storageKey) { + return undefined; + } + return this.storageService.get(storageKey, StorageScope.PROFILE); } async restoreForwarded() { @@ -561,8 +568,9 @@ export class TunnelModel extends Disposable { const key = await this.getStorageKey(); this.restoreListener = this._register(this.storageService.onDidChangeValue(async (e) => { if (e.key === key) { - this.tunnelRestoreValue = Promise.resolve(this.storageService.get(await this.getStorageKey(), StorageScope.PROFILE)); + this.tunnelRestoreValue = Promise.resolve(this.storageService.get(key, StorageScope.PROFILE)); await this.restoreForwarded(); + } })); } @@ -574,7 +582,10 @@ export class TunnelModel extends Disposable { const valueToStore = JSON.stringify(Array.from(this.forwarded.values()).filter(value => value.source.source === TunnelSource.User)); if (valueToStore !== this.knownPortsRestoreValue) { this.knownPortsRestoreValue = valueToStore; - this.storageService.store(await this.getStorageKey(), this.knownPortsRestoreValue, StorageScope.PROFILE, StorageTarget.USER); + const key = await this.getStorageKey(); + if (key) { + this.storageService.store(key, this.knownPortsRestoreValue, StorageScope.PROFILE, StorageTarget.USER); + } } } } From 8ab52fa9e8ec2039741774acda0073c880cb062c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 17 Feb 2023 12:05:30 +0100 Subject: [PATCH 81/85] Add a mechanism to instantiate editor features and adopt it for `SemanticColoringFeature` --- src/vs/editor/browser/editorExtensions.ts | 33 ++++++----- .../services/abstractCodeEditorService.ts | 14 +++++ .../browser/services/codeEditorService.ts | 5 +- .../editor/browser/widget/codeEditorWidget.ts | 1 + .../editor/browser/widget/diffEditorWidget.ts | 1 + src/vs/editor/common/editorFeatures.ts | 29 ++++++++++ src/vs/editor/common/services/model.ts | 3 - src/vs/editor/common/services/modelService.ts | 57 ++++--------------- .../common/services/semanticTokensStyling.ts | 18 ++++++ .../services/semanticTokensStylingService.ts | 41 +++++++++++++ .../browser/viewportSemanticTokens.ts | 6 +- .../standalone/browser/standaloneServices.ts | 16 +++++- .../test/common/services/modelService.test.ts | 13 ++--- .../mainThreadDocumentsAndEditors.test.ts | 8 --- .../test/browser/mainThreadEditors.test.ts | 7 --- .../browser/codeEditor.contribution.ts | 1 + .../codeEditor/browser/editorFeatures.ts | 55 ++++++++++++++++++ .../textsearch.perf.integrationTest.ts | 7 --- .../services/model/common/modelService.ts | 10 +--- src/vs/workbench/workbench.common.main.ts | 1 + 20 files changed, 217 insertions(+), 109 deletions(-) create mode 100644 src/vs/editor/common/editorFeatures.ts create mode 100644 src/vs/editor/common/services/semanticTokensStyling.ts create mode 100644 src/vs/editor/common/services/semanticTokensStylingService.ts create mode 100644 src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index a9fb282cb57..6536ffb9034 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -25,10 +25,9 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ILogService } from 'vs/platform/log/common/log'; - export type ServicesAccessor = InstantiationServicesAccessor; -export type IEditorContributionCtor = IConstructorSignature; -export type IDiffEditorContributionCtor = IConstructorSignature; +export type EditorContributionCtor = IConstructorSignature; +export type DiffEditorContributionCtor = IConstructorSignature; export const enum EditorContributionInstantiation { /** @@ -65,13 +64,13 @@ export const enum EditorContributionInstantiation { export interface IEditorContributionDescription { readonly id: string; - readonly ctor: IEditorContributionCtor; + readonly ctor: EditorContributionCtor; readonly instantiation: EditorContributionInstantiation; } export interface IDiffEditorContributionDescription { id: string; - ctor: IDiffEditorContributionCtor; + ctor: DiffEditorContributionCtor; } //#region Command @@ -515,10 +514,18 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction); } +/** + * Registers an editor contribution. Editor contributions have a lifecycle which is bound + * to a specific code editor instance. + */ export function registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }, instantiation: EditorContributionInstantiation): void { EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor, instantiation); } +/** + * Registers a diff editor contribution. Diff editor contributions have a lifecycle which + * is bound to a specific diff editor instance. + */ export function registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void { EditorContributionRegistry.INSTANCE.registerDiffEditorContribution(id, ctor); } @@ -555,20 +562,16 @@ class EditorContributionRegistry { public static readonly INSTANCE = new EditorContributionRegistry(); - private readonly editorContributions: IEditorContributionDescription[]; - private readonly diffEditorContributions: IDiffEditorContributionDescription[]; - private readonly editorActions: EditorAction[]; - private readonly editorCommands: { [commandId: string]: EditorCommand }; + private readonly editorContributions: IEditorContributionDescription[] = []; + private readonly diffEditorContributions: IDiffEditorContributionDescription[] = []; + private readonly editorActions: EditorAction[] = []; + private readonly editorCommands: { [commandId: string]: EditorCommand } = Object.create(null); constructor() { - this.editorContributions = []; - this.diffEditorContributions = []; - this.editorActions = []; - this.editorCommands = Object.create(null); } public registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }, instantiation: EditorContributionInstantiation): void { - this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor, instantiation }); + this.editorContributions.push({ id, ctor: ctor as EditorContributionCtor, instantiation }); } public getEditorContributions(): IEditorContributionDescription[] { @@ -576,7 +579,7 @@ class EditorContributionRegistry { } public registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void { - this.diffEditorContributions.push({ id, ctor: ctor as IDiffEditorContributionCtor }); + this.diffEditorContributions.push({ id, ctor: ctor as DiffEditorContributionCtor }); } public getDiffEditorContributions(): IDiffEditorContributionDescription[] { diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index fd7ffee97c5..f6268dfac42 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -21,12 +21,18 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC declare readonly _serviceBrand: undefined; + private readonly _onWillCreateCodeEditor = this._register(new Emitter()); + public readonly onWillCreateCodeEditor = this._onWillCreateCodeEditor.event; + private readonly _onCodeEditorAdd: Emitter = this._register(new Emitter()); public readonly onCodeEditorAdd: Event = this._onCodeEditorAdd.event; private readonly _onCodeEditorRemove: Emitter = this._register(new Emitter()); public readonly onCodeEditorRemove: Event = this._onCodeEditorRemove.event; + private readonly _onWillCreateDiffEditor = this._register(new Emitter()); + public readonly onWillCreateDiffEditor = this._onWillCreateDiffEditor.event; + private readonly _onDiffEditorAdd: Emitter = this._register(new Emitter()); public readonly onDiffEditorAdd: Event = this._onDiffEditorAdd.event; @@ -55,6 +61,10 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC this._globalStyleSheet = null; } + willCreateCodeEditor(): void { + this._onWillCreateCodeEditor.fire(); + } + addCodeEditor(editor: ICodeEditor): void { this._codeEditors[editor.getId()] = editor; this._onCodeEditorAdd.fire(editor); @@ -70,6 +80,10 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return Object.keys(this._codeEditors).map(id => this._codeEditors[id]); } + willCreateDiffEditor(): void { + this._onWillCreateDiffEditor.fire(); + } + addDiffEditor(editor: IDiffEditor): void { this._diffEditors[editor.getId()] = editor; this._onDiffEditorAdd.fire(editor); diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index 9de2e964a03..d7119b2506e 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -17,20 +17,23 @@ export const ICodeEditorService = createDecorator('codeEdito export interface ICodeEditorService { readonly _serviceBrand: undefined; + readonly onWillCreateCodeEditor: Event; readonly onCodeEditorAdd: Event; readonly onCodeEditorRemove: Event; + readonly onWillCreateDiffEditor: Event; readonly onDiffEditorAdd: Event; readonly onDiffEditorRemove: Event; readonly onDidChangeTransientModelProperty: Event; readonly onDecorationTypeRegistered: Event; - + willCreateCodeEditor(): void; addCodeEditor(editor: ICodeEditor): void; removeCodeEditor(editor: ICodeEditor): void; listCodeEditors(): readonly ICodeEditor[]; + willCreateDiffEditor(): void; addDiffEditor(editor: IDiffEditor): void; removeDiffEditor(editor: IDiffEditor): void; listDiffEditors(): readonly IDiffEditor[]; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index bcfe4c68713..25d3e21cbde 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -284,6 +284,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); + codeEditorService.willCreateCodeEditor(); const options = { ..._options }; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 14de272ba9f..49830dc5284 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -252,6 +252,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE @IEditorProgressService private readonly _editorProgressService: IEditorProgressService ) { super(); + codeEditorService.willCreateDiffEditor(); this._documentDiffProvider = this._register(instantiationService.createInstance(WorkerBasedDocumentDiffProvider, options)); this._register(this._documentDiffProvider.onDidChange(e => this._beginUpdateDecorationsSoon())); diff --git a/src/vs/editor/common/editorFeatures.ts b/src/vs/editor/common/editorFeatures.ts new file mode 100644 index 00000000000..99d29778303 --- /dev/null +++ b/src/vs/editor/common/editorFeatures.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrandedService, IConstructorSignature } from 'vs/platform/instantiation/common/instantiation'; + +/** + * A feature that will be loaded when the first code editor is constructed and disposed when the system shuts down. + */ +export interface IEditorFeature { + // Marker Interface +} + +export type EditorFeatureCtor = IConstructorSignature; + +const editorFeatures: EditorFeatureCtor[] = []; + +/** + * Registers an editor feature. Editor features will be instantiated only once, as soon as + * the first code editor is instantiated. + */ +export function registerEditorFeature(ctor: { new(...services: Services): IEditorFeature }): void { + editorFeatures.push(ctor as EditorFeatureCtor); +} + +export function getEditorFeatures(): Iterable { + return editorFeatures.slice(0); +} diff --git a/src/vs/editor/common/services/model.ts b/src/vs/editor/common/services/model.ts index 88cc4606c7e..de627268e69 100644 --- a/src/vs/editor/common/services/model.ts +++ b/src/vs/editor/common/services/model.ts @@ -9,7 +9,6 @@ import { ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/ed import { ILanguageSelection } from 'vs/editor/common/languages/language'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/languages'; -import { SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling'; export const IModelService = createDecorator('modelService'); @@ -32,8 +31,6 @@ export interface IModelService { getModel(resource: URI): ITextModel | null; - getSemanticTokensProviderStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling; - onModelAdded: Event; onModelRemoved: Event; diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index fd8618a7de9..96901dff3eb 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -17,13 +17,12 @@ import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/languages'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language'; -import { IModelService, DocumentTokensProvider } from 'vs/editor/common/services/model'; +import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ILogService } from 'vs/platform/log/common/log'; import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo'; import { StringSHA1 } from 'vs/base/common/hash'; import { isEditStackElement } from 'vs/editor/common/model/editStack'; @@ -36,6 +35,8 @@ import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs import { StopWatch } from 'vs/base/common/stopwatch'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { ISemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStyling'; +import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; export interface IEditorSemanticHighlightingOptions { enabled: true | false | 'configuredByTheme'; @@ -153,30 +154,22 @@ export class ModelService extends Disposable implements IModelService { private readonly _models: { [modelId: string]: ModelData }; private readonly _disposedModels: Map; private _disposedModelsHeapSize: number; - private readonly _semanticStyling: SemanticStyling; constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly _resourcePropertiesService: ITextResourcePropertiesService, - @IThemeService private readonly _themeService: IThemeService, - @ILogService private readonly _logService: ILogService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @ILanguageService private readonly _languageService: ILanguageService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, - @ILanguageFeatureDebounceService private readonly _languageFeatureDebounceService: ILanguageFeatureDebounceService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); this._modelCreationOptionsByLanguageAndResource = Object.create(null); this._models = {}; this._disposedModels = new Map(); this._disposedModelsHeapSize = 0; - this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._languageService, this._logService)); this._register(this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions(e))); this._updateModelOptions(undefined); - - this._register(new SemanticColoringFeature(this._semanticStyling, this, this._themeService, this._configurationService, this._languageFeatureDebounceService, languageFeaturesService)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { @@ -554,10 +547,6 @@ export class ModelService extends Disposable implements IModelService { return modelData.model; } - public getSemanticTokensProviderStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling { - return this._semanticStyling.get(provider); - } - // --- end IModelService protected _schemaShouldMaintainUndoRedoElements(resource: URI) { @@ -647,13 +636,12 @@ export function isSemanticColoringEnabled(model: ITextModel, themeService: IThem return themeService.getColorTheme().semanticHighlighting; } -class SemanticColoringFeature extends Disposable { +export class SemanticColoringFeature extends Disposable { private readonly _watchers: Record; - private readonly _semanticStyling: SemanticStyling; constructor( - semanticStyling: SemanticStyling, + @ISemanticTokensStylingService semanticTokensStylingService: ISemanticTokensStylingService, @IModelService modelService: IModelService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @@ -662,10 +650,9 @@ class SemanticColoringFeature extends Disposable { ) { super(); this._watchers = Object.create(null); - this._semanticStyling = semanticStyling; const register = (model: ITextModel) => { - this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, this._semanticStyling, themeService, languageFeatureDebounceService, languageFeaturesService); + this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService); }; const deregister = (model: ITextModel, modelSemanticColoring: ModelSemanticColoring) => { modelSemanticColoring.dispose(); @@ -713,30 +700,6 @@ class SemanticColoringFeature extends Disposable { } } -class SemanticStyling extends Disposable { - - private _caches: WeakMap; - - constructor( - private readonly _themeService: IThemeService, - private readonly _languageService: ILanguageService, - private readonly _logService: ILogService - ) { - super(); - this._caches = new WeakMap(); - this._register(this._themeService.onDidColorThemeChange(() => { - this._caches = new WeakMap(); - })); - } - - public get(provider: DocumentTokensProvider): SemanticTokensProviderStyling { - if (!this._caches.has(provider)) { - this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._languageService, this._logService)); - } - return this._caches.get(provider)!; - } -} - class SemanticTokensResponse { constructor( public readonly provider: DocumentSemanticTokensProvider, @@ -756,7 +719,6 @@ class ModelSemanticColoring extends Disposable { private _isDisposed: boolean; private readonly _model: ITextModel; - private readonly _semanticStyling: SemanticStyling; private readonly _provider: LanguageFeatureRegistry; private readonly _debounceInformation: IFeatureDebounceInformation; private readonly _fetchDocumentSemanticTokens: RunOnceScheduler; @@ -767,7 +729,7 @@ class ModelSemanticColoring extends Disposable { constructor( model: ITextModel, - stylingProvider: SemanticStyling, + @ISemanticTokensStylingService private readonly _semanticTokensStylingService: ISemanticTokensStylingService, @IThemeService themeService: IThemeService, @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @@ -776,7 +738,6 @@ class ModelSemanticColoring extends Disposable { this._isDisposed = false; this._model = model; - this._semanticStyling = stylingProvider; this._provider = languageFeaturesService.documentSemanticTokensProvider; this._debounceInformation = languageFeatureDebounceService.for(this._provider, 'DocumentSemanticTokens', { min: ModelSemanticColoring.REQUEST_MIN_DELAY, max: ModelSemanticColoring.REQUEST_MAX_DELAY }); this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), ModelSemanticColoring.REQUEST_MIN_DELAY)); @@ -887,7 +848,7 @@ class ModelSemanticColoring extends Disposable { this._setDocumentSemanticTokens(null, null, null, pendingChanges); } else { const { provider, tokens } = res; - const styling = this._semanticStyling.get(provider); + const styling = this._semanticTokensStylingService.getStyling(provider); this._setDocumentSemanticTokens(provider, tokens || null, styling, pendingChanges); } }, (err) => { @@ -1034,3 +995,5 @@ class ModelSemanticColoring extends Disposable { rescheduleIfNeeded(); } } + +registerEditorFeature(SemanticColoringFeature); diff --git a/src/vs/editor/common/services/semanticTokensStyling.ts b/src/vs/editor/common/services/semanticTokensStyling.ts new file mode 100644 index 00000000000..935c9aed528 --- /dev/null +++ b/src/vs/editor/common/services/semanticTokensStyling.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/languages'; +import { SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling'; + +export const ISemanticTokensStylingService = createDecorator('semanticTokensStylingService'); + +export type DocumentTokensProvider = DocumentSemanticTokensProvider | DocumentRangeSemanticTokensProvider; + +export interface ISemanticTokensStylingService { + readonly _serviceBrand: undefined; + + getStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling; +} diff --git a/src/vs/editor/common/services/semanticTokensStylingService.ts b/src/vs/editor/common/services/semanticTokensStylingService.ts new file mode 100644 index 00000000000..8aba1f0a601 --- /dev/null +++ b/src/vs/editor/common/services/semanticTokensStylingService.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { DocumentTokensProvider } from 'vs/editor/common/services/model'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling'; +import { ISemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStyling'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class SemanticTokensStylingService extends Disposable implements ISemanticTokensStylingService { + + public _serviceBrand: undefined; + + private _caches: WeakMap; + + constructor( + @IThemeService private readonly _themeService: IThemeService, + @ILogService private readonly _logService: ILogService, + @ILanguageService private readonly _languageService: ILanguageService, + ) { + super(); + this._caches = new WeakMap(); + this._register(this._themeService.onDidColorThemeChange(() => { + this._caches = new WeakMap(); + })); + } + + public getStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling { + if (!this._caches.has(provider)) { + this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._languageService, this._logService)); + } + return this._caches.get(provider)!; + } +} + +registerSingleton(ISemanticTokensStylingService, SemanticTokensStylingService, InstantiationType.Delayed); diff --git a/src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts b/src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts index 98842559f70..27666fe2dc0 100644 --- a/src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts @@ -11,7 +11,6 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { getDocumentRangeSemanticTokens, hasDocumentRangeSemanticTokensProvider } from 'vs/editor/common/services/getSemanticTokens'; -import { IModelService } from 'vs/editor/common/services/model'; import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from 'vs/editor/common/services/modelService'; import { toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,6 +20,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { DocumentRangeSemanticTokensProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ISemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStyling'; class ViewportSemanticTokensContribution extends Disposable implements IEditorContribution { @@ -38,7 +38,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo constructor( editor: ICodeEditor, - @IModelService private readonly _modelService: IModelService, + @ISemanticTokensStylingService private readonly _semanticTokensStylingService: ISemanticTokensStylingService, @IThemeService private readonly _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, @@ -134,7 +134,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo return; } const { provider, tokens: result } = r; - const styling = this._modelService.getSemanticTokensProviderStyling(provider); + const styling = this._semanticTokensStylingService.getStyling(provider); model.tokenization.setPartialSemanticTokens(range, toMultilineTokens2(result, styling, model.getLanguageId())); }).then(() => this._removeOutstandingRequest(request), () => this._removeOutstandingRequest(request)); return request; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 3aeae6d6fc5..5a9653308cf 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -8,6 +8,8 @@ import 'vs/editor/standalone/browser/standaloneCodeEditorService'; import 'vs/editor/standalone/browser/standaloneLayoutService'; import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/editor/common/services/languageFeatureDebounce'; +import 'vs/editor/common/services/semanticTokensStylingService'; +import 'vs/editor/common/services/languageFeaturesService'; import * as strings from 'vs/base/common/strings'; import * as dom from 'vs/base/browser/dom'; @@ -84,12 +86,12 @@ import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; - -import 'vs/editor/common/services/languageFeaturesService'; import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { WorkspaceEdit } from 'vs/editor/common/languages'; import { AudioCue, IAudioCueService, Sound } from 'vs/platform/audioCues/browser/audioCueService'; import { LogService } from 'vs/platform/log/common/logService'; +import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; +import { onUnexpectedError } from 'vs/base/common/errors'; class SimpleModel implements IResolvedTextEditorModel { @@ -1125,6 +1127,16 @@ export module StandaloneServices { } } + // Instantiate all editor features + const editorFeatures = getEditorFeatures(); + for (const feature of editorFeatures) { + try { + instantiationService.createInstance(feature); + } catch (err) { + onUnexpectedError(err); + } + } + return instantiationService; } } diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 04fa4cc1362..4e3e3c9b368 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -14,7 +14,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; import { DefaultEndOfLine, ITextModel } from 'vs/editor/common/model'; import { createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ModelService } from 'vs/editor/common/services/modelService'; +import { ModelService, SemanticColoringFeature } from 'vs/editor/common/services/modelService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -39,6 +39,7 @@ import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeatu import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { SemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStylingService'; const GENERATE_TESTS = false; @@ -421,18 +422,16 @@ suite('ModelSemanticColoring', () => { themeService.setTheme(new TestColorTheme({}, ColorScheme.DARK, true)); const logService = new NullLogService(); languageFeaturesService = new LanguageFeaturesService(); + languageService = disposables.add(new LanguageService(false)); + const semanticTokensStylingService = disposables.add(new SemanticTokensStylingService(themeService, logService, languageService)); modelService = disposables.add(new ModelService( configService, new TestTextResourcePropertiesService(configService), - themeService, - logService, new UndoRedoService(new TestDialogService(), new TestNotificationService()), - disposables.add(new LanguageService()), + languageService, new TestLanguageConfigurationService(), - new LanguageFeatureDebounceService(logService), - languageFeaturesService )); - languageService = disposables.add(new LanguageService(false)); + disposables.add(new SemanticColoringFeature(semanticTokensStylingService, modelService, themeService, configService, new LanguageFeatureDebounceService(logService), languageFeaturesService)); }); teardown(() => { diff --git a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts index bb18933c165..14ef427ac59 100644 --- a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts @@ -20,7 +20,6 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; @@ -32,8 +31,6 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; suite('MainThreadDocumentsAndEditors', () => { @@ -63,17 +60,12 @@ suite('MainThreadDocumentsAndEditors', () => { const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); const themeService = new TestThemeService(); - const logService = new NullLogService(); modelService = new ModelService( configService, new TestTextResourcePropertiesService(configService), - themeService, - new NullLogService(), undoRedoService, disposables.add(new LanguageService()), new TestLanguageConfigurationService(), - new LanguageFeatureDebounceService(logService), - new LanguageFeaturesService() ); codeEditorService = new TestCodeEditorService(themeService); textFileService = new class extends mock() { diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index 7f9da848d8c..7a9bf86bce9 100644 --- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -52,8 +52,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { LanguageService } from 'vs/editor/common/services/languageService'; -import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { MainThreadBulkEdits } from 'vs/workbench/api/browser/mainThreadBulkEdits'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; @@ -86,17 +84,12 @@ suite('MainThreadEditors', () => { const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); const themeService = new TestThemeService(); - const logService = new NullLogService(); modelService = new ModelService( configService, new TestTextResourcePropertiesService(configService), - themeService, - logService, undoRedoService, disposables.add(new LanguageService()), new TestLanguageConfigurationService(), - new LanguageFeatureDebounceService(logService), - new LanguageFeaturesService() ); const services = new ServiceCollection(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index d10833a37f5..3e332198bf0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -6,6 +6,7 @@ import './menuPreventer'; import './accessibility/accessibility'; import './diffEditorHelper'; +import './editorFeatures'; import './editorSettingsMigration'; import './inspectKeybindings'; import './largeFileOptimizations'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts new file mode 100644 index 00000000000..e51906b362e --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IDisposable } from 'xterm'; + +class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContribution { + + private _instantiated = false; + + constructor( + @ICodeEditorService codeEditorService: ICodeEditorService, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + super(); + + this._register(codeEditorService.onWillCreateCodeEditor(() => this._instantiate())); + this._register(codeEditorService.onWillCreateDiffEditor(() => this._instantiate())); + if (codeEditorService.listCodeEditors().length > 0 || codeEditorService.listDiffEditors().length > 0) { + this._instantiate(); + } + } + + private _instantiate(): void { + if (this._instantiated) { + return; + } + this._instantiated = true; + + // Instantiate all editor features + const editorFeatures = getEditorFeatures(); + for (const feature of editorFeatures) { + try { + const instance = this._instantiationService.createInstance(feature); + if (typeof (instance).dispose === 'function') { + this._register((instance)); + } + } catch (err) { + onUnexpectedError(err); + } + } + } +} + +const workbenchRegistry = Registry.as(Extensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(EditorFeaturesInstantiator, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts index 346fa88d3cb..28324a6ebd2 100644 --- a/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts @@ -27,7 +27,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -41,8 +40,6 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; -import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; // declare var __dirname: string; @@ -85,13 +82,9 @@ suite.skip('TextSearch performance (integration)', () => { new ModelService( configurationService, textResourcePropertiesService, - new TestThemeService(), - logService, undoRedoService, new LanguageService(), new TestLanguageConfigurationService(), - new LanguageFeatureDebounceService(logService), - new LanguageFeaturesService() ), ], [ diff --git a/src/vs/workbench/services/model/common/modelService.ts b/src/vs/workbench/services/model/common/modelService.ts index 7765c84da55..570f72df42d 100644 --- a/src/vs/workbench/services/model/common/modelService.ts +++ b/src/vs/workbench/services/model/common/modelService.ts @@ -11,27 +11,19 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; export class WorkbenchModelService extends ModelService { constructor( @IConfigurationService configurationService: IConfigurationService, @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, - @IThemeService themeService: IThemeService, - @ILogService logService: ILogService, @IUndoRedoService undoRedoService: IUndoRedoService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageService languageService: ILanguageService, - @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @IPathService private readonly _pathService: IPathService, ) { - super(configurationService, resourcePropertiesService, themeService, logService, undoRedoService, languageService, languageConfigurationService, languageFeatureDebounceService, languageFeaturesService); + super(configurationService, resourcePropertiesService, undoRedoService, languageService, languageConfigurationService); } protected override _schemaShouldMaintainUndoRedoElements(resource: URI) { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 88e9eabaaaa..0bb83fdd5a7 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -102,6 +102,7 @@ import 'vs/workbench/services/assignment/common/assignmentService'; import 'vs/workbench/services/outline/browser/outlineService'; import 'vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl'; import 'vs/editor/common/services/languageFeaturesService'; +import 'vs/editor/common/services/semanticTokensStylingService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; From 9d44ce60abce26312b7c25b4124da464056eecc4 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 17 Feb 2023 12:33:58 +0100 Subject: [PATCH 82/85] Extract `DocumentSemanticTokensFeature` to a separate file and group files together --- src/vs/editor/common/services/modelService.ts | 393 +----------------- .../browser/documentSemanticTokens.ts | 386 +++++++++++++++++ .../browser/viewportSemanticTokens.ts | 4 +- .../common}/getSemanticTokens.ts | 0 .../common/semanticTokensConfig.ts | 22 + .../browser/documentSemanticTokens.test.ts | 278 +++++++++++++ .../test/browser}/getSemanticTokens.test.ts | 2 +- src/vs/editor/editor.all.ts | 5 +- .../test/common/services/modelService.test.ts | 269 +----------- .../inspectEditorTokens.ts | 2 +- 10 files changed, 697 insertions(+), 664 deletions(-) create mode 100644 src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts rename src/vs/editor/contrib/{viewportSemanticTokens => semanticTokens}/browser/viewportSemanticTokens.ts (97%) rename src/vs/editor/{common/services => contrib/semanticTokens/common}/getSemanticTokens.ts (100%) create mode 100644 src/vs/editor/contrib/semanticTokens/common/semanticTokensConfig.ts create mode 100644 src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts rename src/vs/editor/{test/common/services => contrib/semanticTokens/test/browser}/getSemanticTokens.test.ts (94%) diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index 96901dff3eb..9a8544466d5 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -4,43 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults'; -import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; -import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/languages'; +import { IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo'; import { StringSHA1 } from 'vs/base/common/hash'; import { isEditStackElement } from 'vs/editor/common/model/editStack'; import { Schemas } from 'vs/base/common/network'; -import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; -import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from 'vs/editor/common/services/getSemanticTokens'; import { equals } from 'vs/base/common/objects'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { ISemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStyling'; -import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; - -export interface IEditorSemanticHighlightingOptions { - enabled: true | false | 'configuredByTheme'; -} function MODEL_ID(resource: URI): string { return resource.toString(); @@ -625,375 +608,3 @@ export class ModelService extends Disposable implements IModelService { this._onModelModeChanged.fire({ model, oldLanguageId: oldLanguageId }); } } - -export const SEMANTIC_HIGHLIGHTING_SETTING_ID = 'editor.semanticHighlighting'; - -export function isSemanticColoringEnabled(model: ITextModel, themeService: IThemeService, configurationService: IConfigurationService): boolean { - const setting = configurationService.getValue(SEMANTIC_HIGHLIGHTING_SETTING_ID, { overrideIdentifier: model.getLanguageId(), resource: model.uri })?.enabled; - if (typeof setting === 'boolean') { - return setting; - } - return themeService.getColorTheme().semanticHighlighting; -} - -export class SemanticColoringFeature extends Disposable { - - private readonly _watchers: Record; - - constructor( - @ISemanticTokensStylingService semanticTokensStylingService: ISemanticTokensStylingService, - @IModelService modelService: IModelService, - @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - ) { - super(); - this._watchers = Object.create(null); - - const register = (model: ITextModel) => { - this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService); - }; - const deregister = (model: ITextModel, modelSemanticColoring: ModelSemanticColoring) => { - modelSemanticColoring.dispose(); - delete this._watchers[model.uri.toString()]; - }; - const handleSettingOrThemeChange = () => { - for (const model of modelService.getModels()) { - const curr = this._watchers[model.uri.toString()]; - if (isSemanticColoringEnabled(model, themeService, configurationService)) { - if (!curr) { - register(model); - } - } else { - if (curr) { - deregister(model, curr); - } - } - } - }; - this._register(modelService.onModelAdded((model) => { - if (isSemanticColoringEnabled(model, themeService, configurationService)) { - register(model); - } - })); - this._register(modelService.onModelRemoved((model) => { - const curr = this._watchers[model.uri.toString()]; - if (curr) { - deregister(model, curr); - } - })); - this._register(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(SEMANTIC_HIGHLIGHTING_SETTING_ID)) { - handleSettingOrThemeChange(); - } - })); - this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange)); - } - - override dispose(): void { - // Dispose all watchers - for (const watcher of Object.values(this._watchers)) { - watcher.dispose(); - } - super.dispose(); - } -} - -class SemanticTokensResponse { - constructor( - public readonly provider: DocumentSemanticTokensProvider, - public readonly resultId: string | undefined, - public readonly data: Uint32Array - ) { } - - public dispose(): void { - this.provider.releaseDocumentSemanticTokens(this.resultId); - } -} - -class ModelSemanticColoring extends Disposable { - - public static REQUEST_MIN_DELAY = 300; - public static REQUEST_MAX_DELAY = 2000; - - private _isDisposed: boolean; - private readonly _model: ITextModel; - private readonly _provider: LanguageFeatureRegistry; - private readonly _debounceInformation: IFeatureDebounceInformation; - private readonly _fetchDocumentSemanticTokens: RunOnceScheduler; - private _currentDocumentResponse: SemanticTokensResponse | null; - private _currentDocumentRequestCancellationTokenSource: CancellationTokenSource | null; - private _documentProvidersChangeListeners: IDisposable[]; - private _providersChangedDuringRequest: boolean; - - constructor( - model: ITextModel, - @ISemanticTokensStylingService private readonly _semanticTokensStylingService: ISemanticTokensStylingService, - @IThemeService themeService: IThemeService, - @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - ) { - super(); - - this._isDisposed = false; - this._model = model; - this._provider = languageFeaturesService.documentSemanticTokensProvider; - this._debounceInformation = languageFeatureDebounceService.for(this._provider, 'DocumentSemanticTokens', { min: ModelSemanticColoring.REQUEST_MIN_DELAY, max: ModelSemanticColoring.REQUEST_MAX_DELAY }); - this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), ModelSemanticColoring.REQUEST_MIN_DELAY)); - this._currentDocumentResponse = null; - this._currentDocumentRequestCancellationTokenSource = null; - this._documentProvidersChangeListeners = []; - this._providersChangedDuringRequest = false; - - this._register(this._model.onDidChangeContent(() => { - if (!this._fetchDocumentSemanticTokens.isScheduled()) { - this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); - } - })); - this._register(this._model.onDidChangeLanguage(() => { - // clear any outstanding state - if (this._currentDocumentResponse) { - this._currentDocumentResponse.dispose(); - this._currentDocumentResponse = null; - } - if (this._currentDocumentRequestCancellationTokenSource) { - this._currentDocumentRequestCancellationTokenSource.cancel(); - this._currentDocumentRequestCancellationTokenSource = null; - } - this._setDocumentSemanticTokens(null, null, null, []); - this._fetchDocumentSemanticTokens.schedule(0); - })); - - const bindDocumentChangeListeners = () => { - dispose(this._documentProvidersChangeListeners); - this._documentProvidersChangeListeners = []; - for (const provider of this._provider.all(model)) { - if (typeof provider.onDidChange === 'function') { - this._documentProvidersChangeListeners.push(provider.onDidChange(() => { - if (this._currentDocumentRequestCancellationTokenSource) { - // there is already a request running, - this._providersChangedDuringRequest = true; - return; - } - this._fetchDocumentSemanticTokens.schedule(0); - })); - } - } - }; - bindDocumentChangeListeners(); - this._register(this._provider.onDidChange(() => { - bindDocumentChangeListeners(); - this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); - })); - - this._register(themeService.onDidColorThemeChange(_ => { - // clear out existing tokens - this._setDocumentSemanticTokens(null, null, null, []); - this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); - })); - - this._fetchDocumentSemanticTokens.schedule(0); - } - - public override dispose(): void { - if (this._currentDocumentResponse) { - this._currentDocumentResponse.dispose(); - this._currentDocumentResponse = null; - } - if (this._currentDocumentRequestCancellationTokenSource) { - this._currentDocumentRequestCancellationTokenSource.cancel(); - this._currentDocumentRequestCancellationTokenSource = null; - } - this._setDocumentSemanticTokens(null, null, null, []); - this._isDisposed = true; - - super.dispose(); - } - - private _fetchDocumentSemanticTokensNow(): void { - if (this._currentDocumentRequestCancellationTokenSource) { - // there is already a request running, let it finish... - return; - } - - if (!hasDocumentSemanticTokensProvider(this._provider, this._model)) { - // there is no provider - if (this._currentDocumentResponse) { - // there are semantic tokens set - this._model.tokenization.setSemanticTokens(null, false); - } - return; - } - - const cancellationTokenSource = new CancellationTokenSource(); - const lastProvider = this._currentDocumentResponse ? this._currentDocumentResponse.provider : null; - const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null; - const request = getDocumentSemanticTokens(this._provider, this._model, lastProvider, lastResultId, cancellationTokenSource.token); - this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource; - this._providersChangedDuringRequest = false; - - const pendingChanges: IModelContentChangedEvent[] = []; - const contentChangeListener = this._model.onDidChangeContent((e) => { - pendingChanges.push(e); - }); - - const sw = new StopWatch(false); - request.then((res) => { - this._debounceInformation.update(this._model, sw.elapsed()); - this._currentDocumentRequestCancellationTokenSource = null; - contentChangeListener.dispose(); - - if (!res) { - this._setDocumentSemanticTokens(null, null, null, pendingChanges); - } else { - const { provider, tokens } = res; - const styling = this._semanticTokensStylingService.getStyling(provider); - this._setDocumentSemanticTokens(provider, tokens || null, styling, pendingChanges); - } - }, (err) => { - const isExpectedError = err && (errors.isCancellationError(err) || (typeof err.message === 'string' && err.message.indexOf('busy') !== -1)); - if (!isExpectedError) { - errors.onUnexpectedError(err); - } - - // Semantic tokens eats up all errors and considers errors to mean that the result is temporarily not available - // The API does not have a special error kind to express this... - this._currentDocumentRequestCancellationTokenSource = null; - contentChangeListener.dispose(); - - if (pendingChanges.length > 0 || this._providersChangedDuringRequest) { - // More changes occurred while the request was running - if (!this._fetchDocumentSemanticTokens.isScheduled()) { - this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); - } - } - }); - } - - private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void { - // protect against overflows - length = Math.min(length, dest.length - destOffset, src.length - srcOffset); - for (let i = 0; i < length; i++) { - dest[destOffset + i] = src[srcOffset + i]; - } - } - - private _setDocumentSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticTokensProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { - const currentResponse = this._currentDocumentResponse; - const rescheduleIfNeeded = () => { - if ((pendingChanges.length > 0 || this._providersChangedDuringRequest) && !this._fetchDocumentSemanticTokens.isScheduled()) { - this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); - } - }; - - if (this._currentDocumentResponse) { - this._currentDocumentResponse.dispose(); - this._currentDocumentResponse = null; - } - if (this._isDisposed) { - // disposed! - if (provider && tokens) { - provider.releaseDocumentSemanticTokens(tokens.resultId); - } - return; - } - if (!provider || !styling) { - this._model.tokenization.setSemanticTokens(null, false); - return; - } - if (!tokens) { - this._model.tokenization.setSemanticTokens(null, true); - rescheduleIfNeeded(); - return; - } - - if (isSemanticTokensEdits(tokens)) { - if (!currentResponse) { - // not possible! - this._model.tokenization.setSemanticTokens(null, true); - return; - } - if (tokens.edits.length === 0) { - // nothing to do! - tokens = { - resultId: tokens.resultId, - data: currentResponse.data - }; - } else { - let deltaLength = 0; - for (const edit of tokens.edits) { - deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount; - } - - const srcData = currentResponse.data; - const destData = new Uint32Array(srcData.length + deltaLength); - - let srcLastStart = srcData.length; - let destLastStart = destData.length; - for (let i = tokens.edits.length - 1; i >= 0; i--) { - const edit = tokens.edits[i]; - - if (edit.start > srcData.length) { - styling.warnInvalidEditStart(currentResponse.resultId, tokens.resultId, i, edit.start, srcData.length); - // The edits are invalid and there's no way to recover - this._model.tokenization.setSemanticTokens(null, true); - return; - } - - const copyCount = srcLastStart - (edit.start + edit.deleteCount); - if (copyCount > 0) { - ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount); - destLastStart -= copyCount; - } - - if (edit.data) { - ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length); - destLastStart -= edit.data.length; - } - - srcLastStart = edit.start; - } - - if (srcLastStart > 0) { - ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart); - } - - tokens = { - resultId: tokens.resultId, - data: destData - }; - } - } - - if (isSemanticTokens(tokens)) { - - this._currentDocumentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); - - const result = toMultilineTokens2(tokens, styling, this._model.getLanguageId()); - - // Adjust incoming semantic tokens - if (pendingChanges.length > 0) { - // More changes occurred while the request was running - // We need to: - // 1. Adjust incoming semantic tokens - // 2. Request them again - for (const change of pendingChanges) { - for (const area of result) { - for (const singleChange of change.changes) { - area.applyEdit(singleChange.range, singleChange.text); - } - } - } - } - - this._model.tokenization.setSemanticTokens(result, true); - } else { - this._model.tokenization.setSemanticTokens(null, true); - } - - rescheduleIfNeeded(); - } -} - -registerEditorFeature(SemanticColoringFeature); diff --git a/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts new file mode 100644 index 00000000000..43c004886d0 --- /dev/null +++ b/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts @@ -0,0 +1,386 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import * as errors from 'vs/base/common/errors'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; +import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/languages'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; +import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; +import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { ISemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStyling'; +import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; +import { SEMANTIC_HIGHLIGHTING_SETTING_ID, isSemanticColoringEnabled } from 'vs/editor/contrib/semanticTokens/common/semanticTokensConfig'; + +export class DocumentSemanticTokensFeature extends Disposable { + + private readonly _watchers: Record; + + constructor( + @ISemanticTokensStylingService semanticTokensStylingService: ISemanticTokensStylingService, + @IModelService modelService: IModelService, + @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + super(); + this._watchers = Object.create(null); + + const register = (model: ITextModel) => { + this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService); + }; + const deregister = (model: ITextModel, modelSemanticColoring: ModelSemanticColoring) => { + modelSemanticColoring.dispose(); + delete this._watchers[model.uri.toString()]; + }; + const handleSettingOrThemeChange = () => { + for (const model of modelService.getModels()) { + const curr = this._watchers[model.uri.toString()]; + if (isSemanticColoringEnabled(model, themeService, configurationService)) { + if (!curr) { + register(model); + } + } else { + if (curr) { + deregister(model, curr); + } + } + } + }; + this._register(modelService.onModelAdded((model) => { + if (isSemanticColoringEnabled(model, themeService, configurationService)) { + register(model); + } + })); + this._register(modelService.onModelRemoved((model) => { + const curr = this._watchers[model.uri.toString()]; + if (curr) { + deregister(model, curr); + } + })); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SEMANTIC_HIGHLIGHTING_SETTING_ID)) { + handleSettingOrThemeChange(); + } + })); + this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange)); + } + + override dispose(): void { + // Dispose all watchers + for (const watcher of Object.values(this._watchers)) { + watcher.dispose(); + } + super.dispose(); + } +} + +class ModelSemanticColoring extends Disposable { + + public static REQUEST_MIN_DELAY = 300; + public static REQUEST_MAX_DELAY = 2000; + + private _isDisposed: boolean; + private readonly _model: ITextModel; + private readonly _provider: LanguageFeatureRegistry; + private readonly _debounceInformation: IFeatureDebounceInformation; + private readonly _fetchDocumentSemanticTokens: RunOnceScheduler; + private _currentDocumentResponse: SemanticTokensResponse | null; + private _currentDocumentRequestCancellationTokenSource: CancellationTokenSource | null; + private _documentProvidersChangeListeners: IDisposable[]; + private _providersChangedDuringRequest: boolean; + + constructor( + model: ITextModel, + @ISemanticTokensStylingService private readonly _semanticTokensStylingService: ISemanticTokensStylingService, + @IThemeService themeService: IThemeService, + @ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + super(); + + this._isDisposed = false; + this._model = model; + this._provider = languageFeaturesService.documentSemanticTokensProvider; + this._debounceInformation = languageFeatureDebounceService.for(this._provider, 'DocumentSemanticTokens', { min: ModelSemanticColoring.REQUEST_MIN_DELAY, max: ModelSemanticColoring.REQUEST_MAX_DELAY }); + this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), ModelSemanticColoring.REQUEST_MIN_DELAY)); + this._currentDocumentResponse = null; + this._currentDocumentRequestCancellationTokenSource = null; + this._documentProvidersChangeListeners = []; + this._providersChangedDuringRequest = false; + + this._register(this._model.onDidChangeContent(() => { + if (!this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); + } + })); + this._register(this._model.onDidChangeLanguage(() => { + // clear any outstanding state + if (this._currentDocumentResponse) { + this._currentDocumentResponse.dispose(); + this._currentDocumentResponse = null; + } + if (this._currentDocumentRequestCancellationTokenSource) { + this._currentDocumentRequestCancellationTokenSource.cancel(); + this._currentDocumentRequestCancellationTokenSource = null; + } + this._setDocumentSemanticTokens(null, null, null, []); + this._fetchDocumentSemanticTokens.schedule(0); + })); + + const bindDocumentChangeListeners = () => { + dispose(this._documentProvidersChangeListeners); + this._documentProvidersChangeListeners = []; + for (const provider of this._provider.all(model)) { + if (typeof provider.onDidChange === 'function') { + this._documentProvidersChangeListeners.push(provider.onDidChange(() => { + if (this._currentDocumentRequestCancellationTokenSource) { + // there is already a request running, + this._providersChangedDuringRequest = true; + return; + } + this._fetchDocumentSemanticTokens.schedule(0); + })); + } + } + }; + bindDocumentChangeListeners(); + this._register(this._provider.onDidChange(() => { + bindDocumentChangeListeners(); + this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); + })); + + this._register(themeService.onDidColorThemeChange(_ => { + // clear out existing tokens + this._setDocumentSemanticTokens(null, null, null, []); + this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); + })); + + this._fetchDocumentSemanticTokens.schedule(0); + } + + public override dispose(): void { + if (this._currentDocumentResponse) { + this._currentDocumentResponse.dispose(); + this._currentDocumentResponse = null; + } + if (this._currentDocumentRequestCancellationTokenSource) { + this._currentDocumentRequestCancellationTokenSource.cancel(); + this._currentDocumentRequestCancellationTokenSource = null; + } + this._setDocumentSemanticTokens(null, null, null, []); + this._isDisposed = true; + + super.dispose(); + } + + private _fetchDocumentSemanticTokensNow(): void { + if (this._currentDocumentRequestCancellationTokenSource) { + // there is already a request running, let it finish... + return; + } + + if (!hasDocumentSemanticTokensProvider(this._provider, this._model)) { + // there is no provider + if (this._currentDocumentResponse) { + // there are semantic tokens set + this._model.tokenization.setSemanticTokens(null, false); + } + return; + } + + const cancellationTokenSource = new CancellationTokenSource(); + const lastProvider = this._currentDocumentResponse ? this._currentDocumentResponse.provider : null; + const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null; + const request = getDocumentSemanticTokens(this._provider, this._model, lastProvider, lastResultId, cancellationTokenSource.token); + this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource; + this._providersChangedDuringRequest = false; + + const pendingChanges: IModelContentChangedEvent[] = []; + const contentChangeListener = this._model.onDidChangeContent((e) => { + pendingChanges.push(e); + }); + + const sw = new StopWatch(false); + request.then((res) => { + this._debounceInformation.update(this._model, sw.elapsed()); + this._currentDocumentRequestCancellationTokenSource = null; + contentChangeListener.dispose(); + + if (!res) { + this._setDocumentSemanticTokens(null, null, null, pendingChanges); + } else { + const { provider, tokens } = res; + const styling = this._semanticTokensStylingService.getStyling(provider); + this._setDocumentSemanticTokens(provider, tokens || null, styling, pendingChanges); + } + }, (err) => { + const isExpectedError = err && (errors.isCancellationError(err) || (typeof err.message === 'string' && err.message.indexOf('busy') !== -1)); + if (!isExpectedError) { + errors.onUnexpectedError(err); + } + + // Semantic tokens eats up all errors and considers errors to mean that the result is temporarily not available + // The API does not have a special error kind to express this... + this._currentDocumentRequestCancellationTokenSource = null; + contentChangeListener.dispose(); + + if (pendingChanges.length > 0 || this._providersChangedDuringRequest) { + // More changes occurred while the request was running + if (!this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); + } + } + }); + } + + private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void { + // protect against overflows + length = Math.min(length, dest.length - destOffset, src.length - srcOffset); + for (let i = 0; i < length; i++) { + dest[destOffset + i] = src[srcOffset + i]; + } + } + + private _setDocumentSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticTokensProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { + const currentResponse = this._currentDocumentResponse; + const rescheduleIfNeeded = () => { + if ((pendingChanges.length > 0 || this._providersChangedDuringRequest) && !this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model)); + } + }; + + if (this._currentDocumentResponse) { + this._currentDocumentResponse.dispose(); + this._currentDocumentResponse = null; + } + if (this._isDisposed) { + // disposed! + if (provider && tokens) { + provider.releaseDocumentSemanticTokens(tokens.resultId); + } + return; + } + if (!provider || !styling) { + this._model.tokenization.setSemanticTokens(null, false); + return; + } + if (!tokens) { + this._model.tokenization.setSemanticTokens(null, true); + rescheduleIfNeeded(); + return; + } + + if (isSemanticTokensEdits(tokens)) { + if (!currentResponse) { + // not possible! + this._model.tokenization.setSemanticTokens(null, true); + return; + } + if (tokens.edits.length === 0) { + // nothing to do! + tokens = { + resultId: tokens.resultId, + data: currentResponse.data + }; + } else { + let deltaLength = 0; + for (const edit of tokens.edits) { + deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount; + } + + const srcData = currentResponse.data; + const destData = new Uint32Array(srcData.length + deltaLength); + + let srcLastStart = srcData.length; + let destLastStart = destData.length; + for (let i = tokens.edits.length - 1; i >= 0; i--) { + const edit = tokens.edits[i]; + + if (edit.start > srcData.length) { + styling.warnInvalidEditStart(currentResponse.resultId, tokens.resultId, i, edit.start, srcData.length); + // The edits are invalid and there's no way to recover + this._model.tokenization.setSemanticTokens(null, true); + return; + } + + const copyCount = srcLastStart - (edit.start + edit.deleteCount); + if (copyCount > 0) { + ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount); + destLastStart -= copyCount; + } + + if (edit.data) { + ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length); + destLastStart -= edit.data.length; + } + + srcLastStart = edit.start; + } + + if (srcLastStart > 0) { + ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart); + } + + tokens = { + resultId: tokens.resultId, + data: destData + }; + } + } + + if (isSemanticTokens(tokens)) { + + this._currentDocumentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); + + const result = toMultilineTokens2(tokens, styling, this._model.getLanguageId()); + + // Adjust incoming semantic tokens + if (pendingChanges.length > 0) { + // More changes occurred while the request was running + // We need to: + // 1. Adjust incoming semantic tokens + // 2. Request them again + for (const change of pendingChanges) { + for (const area of result) { + for (const singleChange of change.changes) { + area.applyEdit(singleChange.range, singleChange.text); + } + } + } + } + + this._model.tokenization.setSemanticTokens(result, true); + } else { + this._model.tokenization.setSemanticTokens(null, true); + } + + rescheduleIfNeeded(); + } +} + +class SemanticTokensResponse { + constructor( + public readonly provider: DocumentSemanticTokensProvider, + public readonly resultId: string | undefined, + public readonly data: Uint32Array + ) { } + + public dispose(): void { + this.provider.releaseDocumentSemanticTokens(this.resultId); + } +} + +registerEditorFeature(DocumentSemanticTokensFeature); diff --git a/src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts similarity index 97% rename from src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts rename to src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts index 27666fe2dc0..ccf205ae1b6 100644 --- a/src/vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts @@ -10,8 +10,8 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { getDocumentRangeSemanticTokens, hasDocumentRangeSemanticTokensProvider } from 'vs/editor/common/services/getSemanticTokens'; -import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from 'vs/editor/common/services/modelService'; +import { getDocumentRangeSemanticTokens, hasDocumentRangeSemanticTokensProvider } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; +import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from 'vs/editor/contrib/semanticTokens/common/semanticTokensConfig'; import { toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IThemeService } from 'vs/platform/theme/common/themeService'; diff --git a/src/vs/editor/common/services/getSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts similarity index 100% rename from src/vs/editor/common/services/getSemanticTokens.ts rename to src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts diff --git a/src/vs/editor/contrib/semanticTokens/common/semanticTokensConfig.ts b/src/vs/editor/contrib/semanticTokens/common/semanticTokensConfig.ts new file mode 100644 index 00000000000..ebf4f3b2932 --- /dev/null +++ b/src/vs/editor/contrib/semanticTokens/common/semanticTokensConfig.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextModel } from 'vs/editor/common/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +export const SEMANTIC_HIGHLIGHTING_SETTING_ID = 'editor.semanticHighlighting'; + +export interface IEditorSemanticHighlightingOptions { + enabled: true | false | 'configuredByTheme'; +} + +export function isSemanticColoringEnabled(model: ITextModel, themeService: IThemeService, configurationService: IConfigurationService): boolean { + const setting = configurationService.getValue(SEMANTIC_HIGHLIGHTING_SETTING_ID, { overrideIdentifier: model.getLanguageId(), resource: model.uri })?.enabled; + if (typeof setting === 'boolean') { + return setting; + } + return themeService.getColorTheme().semanticHighlighting; +} diff --git a/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts b/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts new file mode 100644 index 00000000000..9a4c4876426 --- /dev/null +++ b/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts @@ -0,0 +1,278 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import { ModelService } from 'vs/editor/common/services/modelService'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Barrier, timeout } from 'vs/base/common/async'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { getDocumentSemanticTokens, isSemanticTokens } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; +import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { SemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStylingService'; +import { DocumentSemanticTokensFeature } from 'vs/editor/contrib/semanticTokens/browser/documentSemanticTokens'; + +suite('ModelSemanticColoring', () => { + + const disposables = new DisposableStore(); + let modelService: IModelService; + let languageService: ILanguageService; + let languageFeaturesService: ILanguageFeaturesService; + + setup(() => { + const configService = new TestConfigurationService({ editor: { semanticHighlighting: true } }); + const themeService = new TestThemeService(); + themeService.setTheme(new TestColorTheme({}, ColorScheme.DARK, true)); + const logService = new NullLogService(); + languageFeaturesService = new LanguageFeaturesService(); + languageService = disposables.add(new LanguageService(false)); + const semanticTokensStylingService = disposables.add(new SemanticTokensStylingService(themeService, logService, languageService)); + modelService = disposables.add(new ModelService( + configService, + new TestTextResourcePropertiesService(configService), + new UndoRedoService(new TestDialogService(), new TestNotificationService()), + languageService, + new TestLanguageConfigurationService(), + )); + disposables.add(new DocumentSemanticTokensFeature(semanticTokensStylingService, modelService, themeService, configService, new LanguageFeatureDebounceService(logService), languageFeaturesService)); + }); + + teardown(() => { + disposables.clear(); + }); + + test('DocumentSemanticTokens should be fetched when the result is empty if there are pending changes', async () => { + await runWithFakedTimers({}, async () => { + + disposables.add(languageService.registerLanguage({ id: 'testMode' })); + + const inFirstCall = new Barrier(); + const delayFirstResult = new Barrier(); + const secondResultProvided = new Barrier(); + let callCount = 0; + + disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider { + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class'], tokenModifiers: [] }; + } + async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { + callCount++; + if (callCount === 1) { + assert.ok('called once'); + inFirstCall.open(); + await delayFirstResult.wait(); + await timeout(0); // wait for the simple scheduler to fire to check that we do actually get rescheduled + return null; + } + if (callCount === 2) { + assert.ok('called twice'); + secondResultProvided.open(); + return null; + } + assert.fail('Unexpected call'); + } + releaseDocumentSemanticTokens(resultId: string | undefined): void { + } + })); + + const textModel = disposables.add(modelService.createModel('Hello world', languageService.createById('testMode'))); + + // wait for the provider to be called + await inFirstCall.wait(); + + // the provider is now in the provide call + // change the text buffer while the provider is running + textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'x' }]); + + // let the provider finish its first result + delayFirstResult.open(); + + // we need to check that the provider is called again, even if it returns null + await secondResultProvided.wait(); + + // assert that it got called twice + assert.strictEqual(callCount, 2); + }); + }); + + test('issue #149412: VS Code hangs when bad semantic token data is received', async () => { + await runWithFakedTimers({}, async () => { + + disposables.add(languageService.registerLanguage({ id: 'testMode' })); + + let lastResult: SemanticTokens | SemanticTokensEdits | null = null; + + disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider { + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class'], tokenModifiers: [] }; + } + async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { + if (!lastResultId) { + // this is the first call + lastResult = { + resultId: '1', + data: new Uint32Array([4294967293, 0, 7, 16, 0, 1, 4, 3, 11, 1]) + }; + } else { + // this is the second call + lastResult = { + resultId: '2', + edits: [{ + start: 4294967276, + deleteCount: 0, + data: new Uint32Array([2, 0, 3, 11, 0]) + }] + }; + } + return lastResult; + } + releaseDocumentSemanticTokens(resultId: string | undefined): void { + } + })); + + const textModel = disposables.add(modelService.createModel('', languageService.createById('testMode'))); + + // wait for the semantic tokens to be fetched + await Event.toPromise(textModel.onDidChangeTokens); + assert.strictEqual(lastResult!.resultId, '1'); + + // edit the text + textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'foo' }]); + + // wait for the semantic tokens to be fetched again + await Event.toPromise(textModel.onDidChangeTokens); + assert.strictEqual(lastResult!.resultId, '2'); + }); + }); + + test('issue #161573: onDidChangeSemanticTokens doesn\'t consistently trigger provideDocumentSemanticTokens', async () => { + await runWithFakedTimers({}, async () => { + + disposables.add(languageService.registerLanguage({ id: 'testMode' })); + + const emitter = new Emitter(); + let requestCount = 0; + disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider { + onDidChange = emitter.event; + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class'], tokenModifiers: [] }; + } + async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { + requestCount++; + if (requestCount === 1) { + await timeout(1000); + // send a change event + emitter.fire(); + await timeout(1000); + return null; + } + return null; + } + releaseDocumentSemanticTokens(resultId: string | undefined): void { + } + })); + + disposables.add(modelService.createModel('', languageService.createById('testMode'))); + + await timeout(5000); + assert.deepStrictEqual(requestCount, 2); + }); + }); + + test('DocumentSemanticTokens should be pick the token provider with actual items', async () => { + await runWithFakedTimers({}, async () => { + + let callCount = 0; + disposables.add(languageService.registerLanguage({ id: 'testMode2' })); + disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode2', new class implements DocumentSemanticTokensProvider { + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class1'], tokenModifiers: [] }; + } + async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { + callCount++; + // For a secondary request return a different value + if (lastResultId) { + return { + data: new Uint32Array([2, 1, 1, 1, 1, 0, 2, 1, 1, 1]) + }; + } + return { + resultId: '1', + data: new Uint32Array([0, 1, 1, 1, 1, 0, 2, 1, 1, 1]) + }; + } + releaseDocumentSemanticTokens(resultId: string | undefined): void { + } + })); + disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode2', new class implements DocumentSemanticTokensProvider { + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class2'], tokenModifiers: [] }; + } + async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { + callCount++; + return null; + } + releaseDocumentSemanticTokens(resultId: string | undefined): void { + } + })); + + function toArr(arr: Uint32Array): number[] { + const result: number[] = []; + for (let i = 0; i < arr.length; i++) { + result[i] = arr[i]; + } + return result; + } + + const textModel = modelService.createModel('Hello world 2', languageService.createById('testMode2')); + try { + let result = await getDocumentSemanticTokens(languageFeaturesService.documentSemanticTokensProvider, textModel, null, null, CancellationToken.None); + assert.ok(result, `We should have tokens (1)`); + assert.ok(result.tokens, `Tokens are found from multiple providers (1)`); + assert.ok(isSemanticTokens(result.tokens), `Tokens are full (1)`); + assert.ok(result.tokens.resultId, `Token result id found from multiple providers (1)`); + assert.deepStrictEqual(toArr(result.tokens.data), [0, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (1)`); + assert.deepStrictEqual(callCount, 2, `Called both token providers (1)`); + assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (1)`); + + // Make a second request. Make sure we get the secondary value + result = await getDocumentSemanticTokens(languageFeaturesService.documentSemanticTokensProvider, textModel, result.provider, result.tokens.resultId, CancellationToken.None); + assert.ok(result, `We should have tokens (2)`); + assert.ok(result.tokens, `Tokens are found from multiple providers (2)`); + assert.ok(isSemanticTokens(result.tokens), `Tokens are full (2)`); + assert.ok(!result.tokens.resultId, `Token result id found from multiple providers (2)`); + assert.deepStrictEqual(toArr(result.tokens.data), [2, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (2)`); + assert.deepStrictEqual(callCount, 4, `Called both token providers (2)`); + assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (2)`); + } finally { + disposables.clear(); + + // Wait for scheduler to finish + await timeout(0); + + // Now dispose the text model + textModel.dispose(); + } + }); + }); +}); diff --git a/src/vs/editor/test/common/services/getSemanticTokens.test.ts b/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts similarity index 94% rename from src/vs/editor/test/common/services/getSemanticTokens.test.ts rename to src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts index aff59d80eea..8cb75776638 100644 --- a/src/vs/editor/test/common/services/getSemanticTokens.test.ts +++ b/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts @@ -9,7 +9,7 @@ import { canceled } from 'vs/base/common/errors'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; import { DocumentSemanticTokensProvider, ProviderResult, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; -import { getDocumentSemanticTokens } from 'vs/editor/common/services/getSemanticTokens'; +import { getDocumentSemanticTokens } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 9c960331ab4..38a7a98a345 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -42,16 +42,17 @@ import 'vs/editor/contrib/longLinesHelper/browser/longLinesHelper'; import 'vs/editor/contrib/multicursor/browser/multicursor'; import 'vs/editor/contrib/parameterHints/browser/parameterHints'; import 'vs/editor/contrib/rename/browser/rename'; -import 'vs/editor/contrib/stickyScroll/browser/stickyScrollContribution'; +import 'vs/editor/contrib/semanticTokens/browser/documentSemanticTokens'; +import 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens'; import 'vs/editor/contrib/smartSelect/browser/smartSelect'; import 'vs/editor/contrib/snippet/browser/snippetController2'; +import 'vs/editor/contrib/stickyScroll/browser/stickyScrollContribution'; import 'vs/editor/contrib/suggest/browser/suggestController'; import 'vs/editor/contrib/suggest/browser/suggestInlineCompletions'; import 'vs/editor/contrib/tokenization/browser/tokenization'; import 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; import 'vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter'; import 'vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators'; -import 'vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens'; import 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; import 'vs/editor/contrib/wordOperations/browser/wordOperations'; import 'vs/editor/contrib/wordPartOperations/browser/wordPartOperations'; diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 4e3e3c9b368..07c303ba1b1 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -5,41 +5,21 @@ import * as assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; -import { Emitter, Event } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; -import { DefaultEndOfLine, ITextModel } from 'vs/editor/common/model'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; import { createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ModelService, SemanticColoringFeature } from 'vs/editor/common/services/modelService'; +import { ModelService } from 'vs/editor/common/services/modelService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; -import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; -import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Barrier, timeout } from 'vs/base/common/async'; -import { LanguageService } from 'vs/editor/common/services/languageService'; -import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; -import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -import { getDocumentSemanticTokens, isSemanticTokens } from 'vs/editor/common/services/getSemanticTokens'; -import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; -import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { SemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStylingService'; const GENERATE_TESTS = false; @@ -409,251 +389,6 @@ suite('ModelService', () => { }); }); -suite('ModelSemanticColoring', () => { - - const disposables = new DisposableStore(); - let modelService: IModelService; - let languageService: ILanguageService; - let languageFeaturesService: ILanguageFeaturesService; - - setup(() => { - const configService = new TestConfigurationService({ editor: { semanticHighlighting: true } }); - const themeService = new TestThemeService(); - themeService.setTheme(new TestColorTheme({}, ColorScheme.DARK, true)); - const logService = new NullLogService(); - languageFeaturesService = new LanguageFeaturesService(); - languageService = disposables.add(new LanguageService(false)); - const semanticTokensStylingService = disposables.add(new SemanticTokensStylingService(themeService, logService, languageService)); - modelService = disposables.add(new ModelService( - configService, - new TestTextResourcePropertiesService(configService), - new UndoRedoService(new TestDialogService(), new TestNotificationService()), - languageService, - new TestLanguageConfigurationService(), - )); - disposables.add(new SemanticColoringFeature(semanticTokensStylingService, modelService, themeService, configService, new LanguageFeatureDebounceService(logService), languageFeaturesService)); - }); - - teardown(() => { - disposables.clear(); - }); - - test('DocumentSemanticTokens should be fetched when the result is empty if there are pending changes', async () => { - await runWithFakedTimers({}, async () => { - - disposables.add(languageService.registerLanguage({ id: 'testMode' })); - - const inFirstCall = new Barrier(); - const delayFirstResult = new Barrier(); - const secondResultProvided = new Barrier(); - let callCount = 0; - - disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider { - getLegend(): SemanticTokensLegend { - return { tokenTypes: ['class'], tokenModifiers: [] }; - } - async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { - callCount++; - if (callCount === 1) { - assert.ok('called once'); - inFirstCall.open(); - await delayFirstResult.wait(); - await timeout(0); // wait for the simple scheduler to fire to check that we do actually get rescheduled - return null; - } - if (callCount === 2) { - assert.ok('called twice'); - secondResultProvided.open(); - return null; - } - assert.fail('Unexpected call'); - } - releaseDocumentSemanticTokens(resultId: string | undefined): void { - } - })); - - const textModel = disposables.add(modelService.createModel('Hello world', languageService.createById('testMode'))); - - // wait for the provider to be called - await inFirstCall.wait(); - - // the provider is now in the provide call - // change the text buffer while the provider is running - textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'x' }]); - - // let the provider finish its first result - delayFirstResult.open(); - - // we need to check that the provider is called again, even if it returns null - await secondResultProvided.wait(); - - // assert that it got called twice - assert.strictEqual(callCount, 2); - }); - }); - - test('issue #149412: VS Code hangs when bad semantic token data is received', async () => { - await runWithFakedTimers({}, async () => { - - disposables.add(languageService.registerLanguage({ id: 'testMode' })); - - let lastResult: SemanticTokens | SemanticTokensEdits | null = null; - - disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider { - getLegend(): SemanticTokensLegend { - return { tokenTypes: ['class'], tokenModifiers: [] }; - } - async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { - if (!lastResultId) { - // this is the first call - lastResult = { - resultId: '1', - data: new Uint32Array([4294967293, 0, 7, 16, 0, 1, 4, 3, 11, 1]) - }; - } else { - // this is the second call - lastResult = { - resultId: '2', - edits: [{ - start: 4294967276, - deleteCount: 0, - data: new Uint32Array([2, 0, 3, 11, 0]) - }] - }; - } - return lastResult; - } - releaseDocumentSemanticTokens(resultId: string | undefined): void { - } - })); - - const textModel = disposables.add(modelService.createModel('', languageService.createById('testMode'))); - - // wait for the semantic tokens to be fetched - await Event.toPromise(textModel.onDidChangeTokens); - assert.strictEqual(lastResult!.resultId, '1'); - - // edit the text - textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'foo' }]); - - // wait for the semantic tokens to be fetched again - await Event.toPromise(textModel.onDidChangeTokens); - assert.strictEqual(lastResult!.resultId, '2'); - }); - }); - - test('issue #161573: onDidChangeSemanticTokens doesn\'t consistently trigger provideDocumentSemanticTokens', async () => { - await runWithFakedTimers({}, async () => { - - disposables.add(languageService.registerLanguage({ id: 'testMode' })); - - const emitter = new Emitter(); - let requestCount = 0; - disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider { - onDidChange = emitter.event; - getLegend(): SemanticTokensLegend { - return { tokenTypes: ['class'], tokenModifiers: [] }; - } - async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { - requestCount++; - if (requestCount === 1) { - await timeout(1000); - // send a change event - emitter.fire(); - await timeout(1000); - return null; - } - return null; - } - releaseDocumentSemanticTokens(resultId: string | undefined): void { - } - })); - - disposables.add(modelService.createModel('', languageService.createById('testMode'))); - - await timeout(5000); - assert.deepStrictEqual(requestCount, 2); - }); - }); - - test('DocumentSemanticTokens should be pick the token provider with actual items', async () => { - await runWithFakedTimers({}, async () => { - - let callCount = 0; - disposables.add(languageService.registerLanguage({ id: 'testMode2' })); - disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode2', new class implements DocumentSemanticTokensProvider { - getLegend(): SemanticTokensLegend { - return { tokenTypes: ['class1'], tokenModifiers: [] }; - } - async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { - callCount++; - // For a secondary request return a different value - if (lastResultId) { - return { - data: new Uint32Array([2, 1, 1, 1, 1, 0, 2, 1, 1, 1]) - }; - } - return { - resultId: '1', - data: new Uint32Array([0, 1, 1, 1, 1, 0, 2, 1, 1, 1]) - }; - } - releaseDocumentSemanticTokens(resultId: string | undefined): void { - } - })); - disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode2', new class implements DocumentSemanticTokensProvider { - getLegend(): SemanticTokensLegend { - return { tokenTypes: ['class2'], tokenModifiers: [] }; - } - async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise { - callCount++; - return null; - } - releaseDocumentSemanticTokens(resultId: string | undefined): void { - } - })); - - function toArr(arr: Uint32Array): number[] { - const result: number[] = []; - for (let i = 0; i < arr.length; i++) { - result[i] = arr[i]; - } - return result; - } - - const textModel = modelService.createModel('Hello world 2', languageService.createById('testMode2')); - try { - let result = await getDocumentSemanticTokens(languageFeaturesService.documentSemanticTokensProvider, textModel, null, null, CancellationToken.None); - assert.ok(result, `We should have tokens (1)`); - assert.ok(result.tokens, `Tokens are found from multiple providers (1)`); - assert.ok(isSemanticTokens(result.tokens), `Tokens are full (1)`); - assert.ok(result.tokens.resultId, `Token result id found from multiple providers (1)`); - assert.deepStrictEqual(toArr(result.tokens.data), [0, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (1)`); - assert.deepStrictEqual(callCount, 2, `Called both token providers (1)`); - assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (1)`); - - // Make a second request. Make sure we get the secondary value - result = await getDocumentSemanticTokens(languageFeaturesService.documentSemanticTokensProvider, textModel, result.provider, result.tokens.resultId, CancellationToken.None); - assert.ok(result, `We should have tokens (2)`); - assert.ok(result.tokens, `Tokens are found from multiple providers (2)`); - assert.ok(isSemanticTokens(result.tokens), `Tokens are full (2)`); - assert.ok(!result.tokens.resultId, `Token result id found from multiple providers (2)`); - assert.deepStrictEqual(toArr(result.tokens.data), [2, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (2)`); - assert.deepStrictEqual(callCount, 4, `Called both token providers (2)`); - assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (2)`); - } finally { - disposables.clear(); - - // Wait for scheduler to finish - await timeout(0); - - // Now dispose the text model - textModel.dispose(); - } - }); - }); -}); - function assertComputeEdits(lines1: string[], lines2: string[]): void { const model = createTextModel(lines1.join('\n')); const textBuffer = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF).textBuffer; diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index e2059ecf3ea..695abe3c784 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -28,7 +28,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateThemingRuleDefinitions } from 'vs/workbench/services/themes/common/colorThemeData'; import { SemanticTokenRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { SEMANTIC_HIGHLIGHTING_SETTING_ID, IEditorSemanticHighlightingOptions } from 'vs/editor/common/services/modelService'; +import { SEMANTIC_HIGHLIGHTING_SETTING_ID, IEditorSemanticHighlightingOptions } from 'vs/editor/contrib/semanticTokens/common/semanticTokensConfig'; import { Schemas } from 'vs/base/common/network'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; From ce978df63f369a062a075a4b86dd4fb6aa69b5bf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 17 Feb 2023 15:51:56 +0100 Subject: [PATCH 83/85] `activeEditorIsPinned` is not set when editor is auto-pinned by `workbench.action.moveEditorLeftInGroup` (fix #174163) (#174679) --- .../common/editor/editorGroupModel.ts | 13 +++++- .../parts/editor/editorGroupModel.test.ts | 40 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 4384de2cc62..a65dc729932 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -551,6 +551,7 @@ export class EditorGroupModel extends Disposable { } const editor = this.editors[index]; + const sticky = this.sticky; // Adjust sticky index: editor moved out of sticky state into unsticky state if (this.isSticky(index) && toIndex > this.sticky) { @@ -566,7 +567,7 @@ export class EditorGroupModel extends Disposable { this.editors.splice(index, 1); this.editors.splice(toIndex, 0, editor); - // Event + // Move Event const event: IGroupEditorMoveEvent = { kind: GroupModelChangeKind.EDITOR_MOVE, editor, @@ -575,6 +576,16 @@ export class EditorGroupModel extends Disposable { }; this._onDidModelChange.fire(event); + // Sticky Event (if sticky changed as part of the move) + if (sticky !== this.sticky) { + const event: IGroupEditorChangeEvent = { + kind: GroupModelChangeKind.EDITOR_STICKY, + editor, + editorIndex: toIndex + }; + this._onDidModelChange.fire(event); + } + return editor; } diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 33e0061620d..1b70d16389c 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -2124,7 +2124,7 @@ suite('EditorGroupModel', () => { assert.strictEqual(group.indexOf(input2), 1); assert.strictEqual(group.indexOf(input3), 2); - group.moveEditor(input1, 2); // moved out of sticky range + group.moveEditor(input1, 2); // moved out of sticky range// assert.strictEqual(group.isSticky(input1), false); assert.strictEqual(group.isSticky(input2), true); assert.strictEqual(group.isSticky(input3), false); @@ -2171,7 +2171,7 @@ suite('EditorGroupModel', () => { assert.strictEqual(group.indexOf(input2), 1); assert.strictEqual(group.indexOf(input3), 2); - group.moveEditor(input3, 0); // moved into sticky range + group.moveEditor(input3, 0); // moved into sticky range// assert.strictEqual(group.isSticky(input1), true); assert.strictEqual(group.isSticky(input2), false); assert.strictEqual(group.isSticky(input3), true); @@ -2325,4 +2325,40 @@ suite('EditorGroupModel', () => { assert.strictEqual(group2Events.opened[1].editor, input2group2); assert.strictEqual(group2Events.opened[1].editorIndex, 1); }); + + test('moving editor sends sticky event when sticky changes', () => { + const group1 = createEditorGroupModel(); + + const input1group1 = input(); + const input2group1 = input(); + const input3group1 = input(); + + // Open all the editors + group1.openEditor(input1group1, { pinned: true, active: true, index: 0, sticky: true }); + group1.openEditor(input2group1, { pinned: true, active: false, index: 1 }); + group1.openEditor(input3group1, { pinned: true, active: false, index: 2 }); + + const group1Events = groupListener(group1); + + group1.moveEditor(input2group1, 0); + assert.strictEqual(group1Events.sticky[0].editor, input2group1); + assert.strictEqual(group1Events.sticky[0].editorIndex, 0); + + const group2 = createEditorGroupModel(); + + const input1group2 = input(); + const input2group2 = input(); + const input3group2 = input(); + + // Open all the editors + group2.openEditor(input1group2, { pinned: true, active: true, index: 0, sticky: true }); + group2.openEditor(input2group2, { pinned: true, active: false, index: 1 }); + group2.openEditor(input3group2, { pinned: true, active: false, index: 2 }); + + const group2Events = groupListener(group2); + + group2.moveEditor(input1group2, 1); + assert.strictEqual(group2Events.unsticky[0].editor, input1group2); + assert.strictEqual(group2Events.unsticky[0].editorIndex, 1); + }); }); From d5277e8e2b73134126cabd4fe570a15b821e96c1 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 17 Feb 2023 07:23:00 -0800 Subject: [PATCH 84/85] Ensure language packs are installed on the server side (#174622) --- build/lib/i18n.resources.json | 4 ++ .../languagePacks/browser/languagePacks.ts | 9 ++- .../languagePacks/common/languagePacks.ts | 4 +- .../languagePacks/node/languagePacks.ts | 7 +-- src/vs/server/node/remoteExtensionsScanner.ts | 60 ++++++++++++++++--- src/vs/server/node/serverServices.ts | 4 +- .../api/browser/mainThreadLocalization.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLocalizationService.ts | 2 +- .../extensions/browser/extensionsActions.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../browser/localization.contribution.ts | 5 -- .../browser/localizationsActions.ts | 2 +- .../localization.contribution.ts | 6 +- .../localization/browser/localeService.ts | 35 +++++++++-- .../localization/common/locale.ts | 7 +++ .../electron-sandbox/localeService.ts | 43 +++++++++++-- .../remote/common/remoteExtensionsScanner.ts | 5 +- src/vs/workbench/workbench.desktop.main.ts | 1 + src/vs/workbench/workbench.web.main.ts | 1 + 20 files changed, 156 insertions(+), 49 deletions(-) rename src/vs/workbench/{contrib => services}/localization/browser/localeService.ts (59%) rename src/vs/workbench/{contrib => services}/localization/common/locale.ts (75%) rename src/vs/workbench/{contrib => services}/localization/electron-sandbox/localeService.ts (79%) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 49087e64d02..afe76cf78ad 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -493,6 +493,10 @@ { "name": "vs/workbench/services/userDataProfile", "project": "vscode-profiles" + }, + { + "name": "vs/workbench/services/localization", + "project": "vscode-workbench" } ] } diff --git a/src/vs/platform/languagePacks/browser/languagePacks.ts b/src/vs/platform/languagePacks/browser/languagePacks.ts index f63b7b552fa..62dedb2242a 100644 --- a/src/vs/platform/languagePacks/browser/languagePacks.ts +++ b/src/vs/platform/languagePacks/browser/languagePacks.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Language } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; @@ -20,7 +19,7 @@ export class WebLanguagePacksService extends LanguagePackBaseService { super(extensionGalleryService); } - async getBuiltInExtensionTranslationsUri(id: string): Promise { + async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise { const queryTimeout = new CancellationTokenSource(); setTimeout(() => queryTimeout.cancel(), 1000); @@ -29,7 +28,7 @@ export class WebLanguagePacksService extends LanguagePackBaseService { let result; try { result = await this.extensionGalleryService.query({ - text: `tag:"lp-${Language.value()}"`, + text: `tag:"lp-${language}"`, pageSize: 5 }, queryTimeout.token); } catch (err) { @@ -39,7 +38,7 @@ export class WebLanguagePacksService extends LanguagePackBaseService { const languagePackExtensions = result.firstPage.find(e => e.properties.localizedLanguages?.length); if (!languagePackExtensions) { - this.logService.trace(`No language pack found for language ${Language.value()}`); + this.logService.trace(`No language pack found for language ${language}`); return undefined; } @@ -49,7 +48,7 @@ export class WebLanguagePacksService extends LanguagePackBaseService { const manifest = await this.extensionGalleryService.getManifest(languagePackExtensions, manifestTimeout.token); // Find the translation from the language pack - const localization = manifest?.contributes?.localizations?.find(l => l.languageId === Language.value()); + const localization = manifest?.contributes?.localizations?.find(l => l.languageId === language); const translation = localization?.translations.find(t => t.id === id); if (!translation) { this.logService.trace(`No translation found for id '${id}, in ${manifest?.name}`); diff --git a/src/vs/platform/languagePacks/common/languagePacks.ts b/src/vs/platform/languagePacks/common/languagePacks.ts index b17d660cb06..f682e997d8c 100644 --- a/src/vs/platform/languagePacks/common/languagePacks.ts +++ b/src/vs/platform/languagePacks/common/languagePacks.ts @@ -27,7 +27,7 @@ export interface ILanguagePackService { readonly _serviceBrand: undefined; getAvailableLanguages(): Promise>; getInstalledLanguages(): Promise>; - getBuiltInExtensionTranslationsUri(id: string): Promise; + getBuiltInExtensionTranslationsUri(id: string, language: string): Promise; } export abstract class LanguagePackBaseService extends Disposable implements ILanguagePackService { @@ -37,7 +37,7 @@ export abstract class LanguagePackBaseService extends Disposable implements ILan super(); } - abstract getBuiltInExtensionTranslationsUri(id: string): Promise; + abstract getBuiltInExtensionTranslationsUri(id: string, language: string): Promise; abstract getInstalledLanguages(): Promise>; diff --git a/src/vs/platform/languagePacks/node/languagePacks.ts b/src/vs/platform/languagePacks/node/languagePacks.ts index b88a3103146..0458bb1a712 100644 --- a/src/vs/platform/languagePacks/node/languagePacks.ts +++ b/src/vs/platform/languagePacks/node/languagePacks.ts @@ -16,7 +16,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { ILogService } from 'vs/platform/log/common/log'; import { ILocalizationContribution } from 'vs/platform/extensions/common/extensions'; import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; -import { Language } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; interface ILanguagePack { @@ -50,11 +49,11 @@ export class NativeLanguagePackService extends LanguagePackBaseService { }); } - async getBuiltInExtensionTranslationsUri(id: string): Promise { + async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise { const packs = await this.cache.getLanguagePacks(); - const pack = packs[Language.value()]; + const pack = packs[language]; if (!pack) { - this.logService.warn(`No language pack found for ${Language.value()}`); + this.logService.warn(`No language pack found for ${language}`); return undefined; } diff --git a/src/vs/server/node/remoteExtensionsScanner.ts b/src/vs/server/node/remoteExtensionsScanner.ts index d4b130ba630..d11da3a2d6d 100644 --- a/src/vs/server/node/remoteExtensionsScanner.ts +++ b/src/vs/server/node/remoteExtensionsScanner.ts @@ -12,7 +12,7 @@ import { Event } from 'vs/base/common/event'; import { IURITransformer, transformOutgoingURIs } from 'vs/base/common/uriIpc'; import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyExpression, ContextKeyGreaterEqualsExpr, ContextKeyGreaterExpr, ContextKeyInExpr, ContextKeyNotEqualsExpr, ContextKeyNotExpr, ContextKeyNotInExpr, ContextKeyRegexExpr, ContextKeySmallerEqualsExpr, ContextKeySmallerExpr, IContextKeyExprMapper } from 'vs/platform/contextkey/common/contextkey'; -import { InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -22,6 +22,7 @@ import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentServi import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { Schemas } from 'vs/base/common/network'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { @@ -30,16 +31,18 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS private readonly _whenExtensionsReady: Promise; constructor( - extensionManagementCLI: ExtensionManagementCLI, + private readonly _extensionManagementCLI: ExtensionManagementCLI, environmentService: IServerEnvironmentService, private readonly _userDataProfilesService: IUserDataProfilesService, private readonly _extensionsScannerService: IExtensionsScannerService, private readonly _logService: ILogService, + private readonly _extensionGalleryService: IExtensionGalleryService, + private readonly _languagePackService: ILanguagePackService ) { if (environmentService.args['install-builtin-extension']) { const installOptions: InstallOptions = { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }; performance.mark('code/server/willInstallBuiltinExtensions'); - this._whenExtensionsReady = extensionManagementCLI.installExtensions([], environmentService.args['install-builtin-extension'], installOptions, !!environmentService.args['force']) + this._whenExtensionsReady = _extensionManagementCLI.installExtensions([], environmentService.args['install-builtin-extension'], installOptions, !!environmentService.args['force']) .then(() => performance.mark('code/server/didInstallBuiltinExtensions'), error => { _logService.error(error); }); @@ -51,7 +54,7 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS if (extensionsToInstall) { const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); this._whenExtensionsReady - .then(() => extensionManagementCLI.installExtensions(idsOrVSIX, [], { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }, !!environmentService.args['force'])) + .then(() => _extensionManagementCLI.installExtensions(idsOrVSIX, [], { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }, !!environmentService.args['force'])) .then(null, error => { _logService.error(error); }); @@ -62,7 +65,7 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS return this._whenExtensionsReady; } - async scanExtensions(language?: string, profileLocation?: URI, extensionDevelopmentLocations?: URI[]): Promise { + async scanExtensions(language?: string, profileLocation?: URI, extensionDevelopmentLocations?: URI[], languagePackId?: string): Promise { await this.whenExtensionsReady(); performance.mark('code/server/willScanExtensions'); @@ -72,7 +75,7 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS const extensionDevelopmentPaths = extensionDevelopmentLocations ? extensionDevelopmentLocations.filter(url => url.scheme === Schemas.file).map(url => url.fsPath) : undefined; profileLocation = profileLocation ?? this._userDataProfilesService.defaultProfile.extensionsResource; - const extensions = await this._scanExtensions(profileLocation, language ?? platform.language, extensionDevelopmentPaths); + const extensions = await this._scanExtensions(profileLocation, language ?? platform.language, extensionDevelopmentPaths, languagePackId); this._logService.trace('Scanned Extensions', extensions); this._massageWhenConditions(extensions); @@ -101,8 +104,8 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS return extension; } - private async _scanExtensions(profileLocation: URI, language: string, extensionDevelopmentPath?: string[]): Promise { - // Ensure that the language packs are available + private async _scanExtensions(profileLocation: URI, language: string, extensionDevelopmentPath: string[] | undefined, languagePackId: string | undefined): Promise { + await this._ensureLanguagePackIsInstalled(language, languagePackId); const [builtinExtensions, installedExtensions, developedExtensions] = await Promise.all([ this._scanBuiltinExtensions(language), @@ -139,6 +142,44 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS return scannedExtension ? toExtensionDescription(scannedExtension, false) : null; } + private async _ensureLanguagePackIsInstalled(language: string, languagePackId: string | undefined): Promise { + if ( + // No need to install language packs for the default language + language === platform.LANGUAGE_DEFAULT || + // The extension gallery service needs to be available + !this._extensionGalleryService.isEnabled() + ) { + return; + } + + try { + const installed = await this._languagePackService.getInstalledLanguages(); + if (installed.find(p => p.id === language)) { + this._logService.trace(`Language Pack ${language} is already installed. Skipping language pack installation.`); + return; + } + } catch (err) { + // We tried to see what is installed but failed. We can try installing anyway. + this._logService.error(err); + } + + if (!languagePackId) { + this._logService.trace(`No language pack id provided for language ${language}. Skipping language pack installation.`); + return; + } + + this._logService.trace(`Language Pack ${languagePackId} for language ${language} is not installed. It will be installed now.`); + try { + await this._extensionManagementCLI.installExtensions([languagePackId], [], { isMachineScoped: true }, true, { + log: (s) => this._logService.info(s), + error: (s) => this._logService.error(s) + }); + } catch (err) { + // We tried to install the language pack but failed. We can continue without it thus using the default language. + this._logService.error(err); + } + } + private _massageWhenConditions(extensions: IExtensionDescription[]): void { // Massage "when" conditions which mention `resourceScheme` @@ -269,7 +310,8 @@ export class RemoteExtensionsScannerChannel implements IServerChannel { const language = args[0]; const profileLocation = args[1] ? URI.revive(uriTransformer.transformIncoming(args[1])) : undefined; const extensionDevelopmentPath = Array.isArray(args[2]) ? args[2].map(u => URI.revive(uriTransformer.transformIncoming(u))) : undefined; - const extensions = await this.service.scanExtensions(language, profileLocation, extensionDevelopmentPath); + const languagePackId: string | undefined = args[3]; + const extensions = await this.service.scanExtensions(language, profileLocation, extensionDevelopmentPath, languagePackId); return extensions.map(extension => transformOutgoingURIs(extension, uriTransformer)); } case 'scanSingleExtension': { diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index edb7b0067d6..408b37adf23 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -209,6 +209,8 @@ export async function setupServerServices(connectionToken: ServerConnectionToken instantiationService.invokeFunction(accessor => { const extensionManagementService = accessor.get(INativeServerExtensionManagementService); const extensionsScannerService = accessor.get(IExtensionsScannerService); + const extensionGalleryService = accessor.get(IExtensionGalleryService); + const languagePackService = accessor.get(ILanguagePackService); const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, extensionHostStatusService); socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel); @@ -217,7 +219,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService, configurationService)); - const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI), environmentService, userDataProfilesService, extensionsScannerService, logService); + const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI), environmentService, userDataProfilesService, extensionsScannerService, logService, extensionGalleryService, languagePackService); socketServer.registerChannel(RemoteExtensionsScannerChannelName, new RemoteExtensionsScannerChannel(remoteExtensionsScanner, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); const remoteFileSystemChannel = new RemoteAgentFileSystemProviderChannel(logService, environmentService); diff --git a/src/vs/workbench/api/browser/mainThreadLocalization.ts b/src/vs/workbench/api/browser/mainThreadLocalization.ts index cc532e404ea..af85618a64c 100644 --- a/src/vs/workbench/api/browser/mainThreadLocalization.ts +++ b/src/vs/workbench/api/browser/mainThreadLocalization.ts @@ -21,9 +21,9 @@ export class MainThreadLocalization extends Disposable implements MainThreadLoca super(); } - async $fetchBuiltInBundleUri(id: string): Promise { + async $fetchBuiltInBundleUri(id: string, language: string): Promise { try { - const uri = await this.languagePackService.getBuiltInExtensionTranslationsUri(id); + const uri = await this.languagePackService.getBuiltInExtensionTranslationsUri(id, language); return uri; } catch (e) { return undefined; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index db3d3eccec1..c61347eb893 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2194,7 +2194,7 @@ export interface MainThreadThemingShape extends IDisposable { } export interface MainThreadLocalizationShape extends IDisposable { - $fetchBuiltInBundleUri(id: string): Promise; + $fetchBuiltInBundleUri(id: string, language: string): Promise; $fetchBundleContents(uriComponents: UriComponents): Promise; } diff --git a/src/vs/workbench/api/common/extHostLocalizationService.ts b/src/vs/workbench/api/common/extHostLocalizationService.ts index 7acfa075c65..6ed204b58dd 100644 --- a/src/vs/workbench/api/common/extHostLocalizationService.ts +++ b/src/vs/workbench/api/common/extHostLocalizationService.ts @@ -95,7 +95,7 @@ export class ExtHostLocalizationService implements ExtHostLocalizationShape { private async getBundleLocation(extension: IExtensionDescription): Promise { if (extension.isBuiltin) { - const uri = await this._proxy.$fetchBuiltInBundleUri(extension.identifier.value); + const uri = await this._proxy.$fetchBuiltInBundleUri(extension.identifier.value, this.currentLanguage); return URI.revive(uri); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index d96af56dc17..a329755f732 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -67,7 +67,7 @@ import { flatten } from 'vs/base/common/arrays'; import { fromNow } from 'vs/base/common/date'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { getLocale } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { isString } from 'vs/base/common/types'; import { showWindowLogActionId } from 'vs/workbench/common/logConstants'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index e59656c21ba..c391c705c5f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -47,7 +47,7 @@ import { IExtensionService, IExtensionsStatus, toExtension, toExtensionDescripti import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { isWeb, language } from 'vs/base/common/platform'; import { getLocale } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/contrib/localization/browser/localization.contribution.ts b/src/vs/workbench/contrib/localization/browser/localization.contribution.ts index e529fd5414d..79f2f0a777b 100644 --- a/src/vs/workbench/contrib/localization/browser/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/browser/localization.contribution.ts @@ -4,12 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { WebLocaleService } from 'vs/workbench/contrib/localization/browser/localeService'; import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; - -registerSingleton(ILocaleService, WebLocaleService, InstantiationType.Delayed); // Register action to configure locale and related settings registerAction2(ConfigureDisplayLanguageAction); diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts index 98d642e19e0..1cfb6dd5fa2 100644 --- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts @@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; export class ConfigureDisplayLanguageAction extends Action2 { diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts index b7091c7a560..0156d0a3eca 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts @@ -22,13 +22,9 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; -import { NativeLocaleService } from 'vs/workbench/contrib/localization/electron-sandbox/localeService'; +import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { IProductService } from 'vs/platform/product/common/productService'; -registerSingleton(ILocaleService, NativeLocaleService, InstantiationType.Delayed); - // Register action to configure locale and related settings registerAction2(ConfigureDisplayLanguageAction); registerAction2(ClearDisplayLanguageAction); diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/services/localization/browser/localeService.ts similarity index 59% rename from src/vs/workbench/contrib/localization/browser/localeService.ts rename to src/vs/workbench/services/localization/browser/localeService.ts index cc309e04387..a089f5ee7fb 100644 --- a/src/vs/workbench/contrib/localization/browser/localeService.ts +++ b/src/vs/workbench/services/localization/browser/localeService.ts @@ -4,15 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Language } from 'vs/base/common/platform'; +import { Language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { IActiveLanguagePackService, ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class WebLocaleService implements ILocaleService { declare readonly _serviceBrand: undefined; + static readonly _LOCAL_STORAGE_EXTENSION_ID_KEY = 'vscode.nls.languagePackExtensionId'; + static readonly _LOCAL_STORAGE_LOCALE_KEY = 'vscode.nls.locale'; constructor( @IDialogService private readonly dialogService: IDialogService, @@ -26,9 +30,13 @@ export class WebLocaleService implements ILocaleService { return; } if (locale) { - window.localStorage.setItem('vscode.nls.locale', locale); + window.localStorage.setItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY, locale); + if (languagePackItem.extensionId) { + window.localStorage.setItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY, languagePackItem.extensionId); + } } else { - window.localStorage.removeItem('vscode.nls.locale'); + window.localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); + window.localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); } const restartDialog = await this.dialogService.confirm({ @@ -44,7 +52,8 @@ export class WebLocaleService implements ILocaleService { } async clearLocalePreference(): Promise { - window.localStorage.removeItem('vscode.nls.locale'); + window.localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); + window.localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); if (Language.value() === navigator.language) { return; @@ -62,3 +71,19 @@ export class WebLocaleService implements ILocaleService { } } } + +class WebActiveLanguagePackService implements IActiveLanguagePackService { + _serviceBrand: undefined; + + async getExtensionIdProvidingCurrentLocale(): Promise { + const language = Language.value(); + if (language === LANGUAGE_DEFAULT) { + return undefined; + } + const extensionId = window.localStorage.getItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); + return withNullAsUndefined(extensionId); + } +} + +registerSingleton(ILocaleService, WebLocaleService, InstantiationType.Delayed); +registerSingleton(IActiveLanguagePackService, WebActiveLanguagePackService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/localization/common/locale.ts b/src/vs/workbench/services/localization/common/locale.ts similarity index 75% rename from src/vs/workbench/contrib/localization/common/locale.ts rename to src/vs/workbench/services/localization/common/locale.ts index cd548e5a802..f2adc50c917 100644 --- a/src/vs/workbench/contrib/localization/common/locale.ts +++ b/src/vs/workbench/services/localization/common/locale.ts @@ -13,3 +13,10 @@ export interface ILocaleService { setLocale(languagePackItem: ILanguagePackItem, skipDialog?: boolean): Promise; clearLocalePreference(): Promise; } + +export const IActiveLanguagePackService = createDecorator('activeLanguageService'); + +export interface IActiveLanguagePackService { + readonly _serviceBrand: undefined; + getExtensionIdProvidingCurrentLocale(): Promise; +} diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/services/localization/electron-sandbox/localeService.ts similarity index 79% rename from src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts rename to src/vs/workbench/services/localization/electron-sandbox/localeService.ts index 829cb8ae48e..d7124758e38 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts +++ b/src/vs/workbench/services/localization/electron-sandbox/localeService.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Language } from 'vs/base/common/platform'; +import { Language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { IActiveLanguagePackService, ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewPaneContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; @@ -22,8 +21,19 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProductService } from 'vs/platform/product/common/productService'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export class NativeLocaleService implements ILocaleService { +// duplicate of IExtensionsViewPaneContainer in contrib +interface IExtensionsViewPaneContainer extends IViewPaneContainer { + readonly searchValue: string | undefined; + search(text: string): void; + refresh(): Promise; +} + +// duplicate of VIEWLET_ID in contrib/extensions +const EXTENSIONS_VIEWLET_ID = 'workbench.view.extensions'; + +class NativeLocaleService implements ILocaleService { _serviceBrand: undefined; constructor( @@ -144,3 +154,26 @@ export class NativeLocaleService implements ILocaleService { return confirmed; } } + +// This is its own service because the localeService depends on IJSONEditingService which causes a circular dependency +// Once that's ironed out, we can fold this into the localeService. +class NativeActiveLanguagePackService implements IActiveLanguagePackService { + _serviceBrand: undefined; + + constructor( + @ILanguagePackService private readonly languagePackService: ILanguagePackService + ) { } + + async getExtensionIdProvidingCurrentLocale(): Promise { + const language = Language.value(); + if (language === LANGUAGE_DEFAULT) { + return undefined; + } + const languages = await this.languagePackService.getInstalledLanguages(); + const languagePack = languages.find(l => l.id === language); + return languagePack?.extensionId; + } +} + +registerSingleton(ILocaleService, NativeLocaleService, InstantiationType.Delayed); +registerSingleton(IActiveLanguagePackService, NativeActiveLanguagePackService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts index 7c499158b40..58843aa305f 100644 --- a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts +++ b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts @@ -15,6 +15,7 @@ import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataPr import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IActiveLanguagePackService } from 'vs/workbench/services/localization/common/locale'; class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { @@ -26,6 +27,7 @@ class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, @ILogService private readonly logService: ILogService, + @IActiveLanguagePackService private readonly activeLanguagePackService: IActiveLanguagePackService ) { } whenExtensionsReady(): Promise { @@ -37,10 +39,11 @@ class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService async scanExtensions(): Promise { try { + const languagePack = await this.activeLanguagePackService.getExtensionIdProvidingCurrentLocale(); return await this.withChannel( async (channel) => { const profileLocation = this.userDataProfileService.currentProfile.isDefault ? undefined : (await this.remoteUserDataProfilesService.getRemoteProfile(this.userDataProfileService.currentProfile)).extensionsResource; - const scannedExtensions = await channel.call('scanExtensions', [platform.language, profileLocation, this.environmentService.extensionDevelopmentLocationURI]); + const scannedExtensions = await channel.call('scanExtensions', [platform.language, profileLocation, this.environmentService.extensionDevelopmentLocationURI, languagePack]); scannedExtensions.forEach((extension) => { extension.extensionLocation = URI.revive(extension.extensionLocation); ImplicitActivationEvents.updateManifest(extension); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7d06a90c0cf..114257a66e5 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -62,6 +62,7 @@ import 'vs/workbench/services/localization/electron-sandbox/languagePackService' import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService'; import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; import 'vs/platform/extensionResourceLoader/common/extensionResourceLoaderService'; +import 'vs/workbench/services/localization/electron-sandbox/localeService'; import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index f7c8567da5e..1bdd751ab1b 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -53,6 +53,7 @@ import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; +import 'vs/workbench/services/localization/browser/localeService'; import 'vs/workbench/services/path/browser/pathService'; import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; import 'vs/workbench/services/encryption/browser/encryptionService'; From 550fdeb89906b1671c0a5a8fbbb729c46f7c46a1 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 17 Feb 2023 16:25:31 +0100 Subject: [PATCH 85/85] Comment thread high contrast button borders clipped (#174681) Fixes #172303 --- src/vs/workbench/contrib/comments/browser/media/review.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index b2c0204b7f2..78f8639cf0a 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -43,7 +43,7 @@ } .review-widget .body .review-comment .comment-actions .monaco-toolbar { - height: 21px; + height: 22px; } .review-widget .body .review-comment .comment-title .comment-header-info {