From 1e47b51d79e1816e7692b870e348e0afbce78595 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 8 Nov 2022 08:30:23 -0800 Subject: [PATCH 01/67] Extend extension point handler to collect implicit activation events --- .../platform/extensions/common/extensions.ts | 3 +- .../common/abstractExtensionService.ts | 42 ++++++++++++++++--- .../services/extensions/common/extensions.ts | 5 +++ .../extensions/common/extensionsRegistry.ts | 23 +++++++++- 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index b24736282a4..b0b7687789c 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -399,7 +399,8 @@ export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest browserNlsBundleUris?: { [language: string]: URI }; } -export type IExtensionDescription = Readonly; +type MutableByKey = Pick & Readonly>; +export type IExtensionDescription = MutableByKey; export function isApplicationScopedExtension(manifest: IExtensionManifest): boolean { return isLanguagePackExtension(manifest); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 053cc45f822..9502906a866 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -15,8 +15,8 @@ import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementServ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, IImplicitActivationEvents } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionActivationEventsCollector, ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; import { createExtensionHostManager, IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -153,6 +153,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private readonly _installedExtensionsReady: Barrier; private readonly _isDev: boolean; private readonly _extensionsMessages: Map; + private readonly _extensionsImplicitActivationEvents: Map>; private readonly _allRequestedActivateEvents = new Set(); private readonly _proposedApiController: ProposedApiController; private readonly _isExtensionDevHost: boolean; @@ -206,6 +207,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._installedExtensionsReady = new Barrier(); this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; this._extensionsMessages = new Map(); + this._extensionsImplicitActivationEvents = new Map>(); this._proposedApiController = _instantiationService.createInstance(ProposedApiController); this._extensionHostManagers = []; @@ -1211,19 +1213,48 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); + const implicitActivationEventsHandler = (info: IImplicitActivationEvents) => this._handleImplicitExtensionActivationEvents(info); const availableExtensions = this._registry.getAllExtensionDescriptions(); const extensionPoints = ExtensionsRegistry.getExtensionPoints(); perf.mark('code/willHandleExtensionPoints'); for (const extensionPoint of extensionPoints) { if (affectedExtensionPoints[extensionPoint.name]) { perf.mark(`code/willHandleExtensionPoint/${extensionPoint.name}`); - AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler); + AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler, implicitActivationEventsHandler); perf.mark(`code/didHandleExtensionPoint/${extensionPoint.name}`); } } + this._combineImplicitExtensionActivationEvents(); perf.mark('code/didHandleExtensionPoints'); } + private _combineImplicitExtensionActivationEvents() { + for (const [extensionKey, implicitActivationEvents] of this._extensionsImplicitActivationEvents.entries()) { + const extensionDescription = this._registry.getExtensionDescription(extensionKey); + if (!extensionDescription) { + continue; + } + + for (const activationEvents of (extensionDescription?.activationEvents ?? [])) { + implicitActivationEvents.add(activationEvents); + } + extensionDescription.activationEvents = Array.from(implicitActivationEvents); + } + } + + private _handleImplicitExtensionActivationEvents(extensionActivationEventsInfo: IImplicitActivationEvents) { + const extensionKey = ExtensionIdentifier.toKey(extensionActivationEventsInfo.extensionId); + + if (!this._extensionsImplicitActivationEvents.has(extensionKey)) { + this._extensionsImplicitActivationEvents.set(extensionKey, new Set()); + } + const implicitActivationEvents = this._extensionsImplicitActivationEvents.get(extensionKey); + + for (const activationEvent of extensionActivationEventsInfo.implicitActivationEvents) { + implicitActivationEvents?.add(activationEvent); + } + } + private _handleExtensionPointMessage(msg: IMessage) { const extensionKey = ExtensionIdentifier.toKey(msg.extensionId); @@ -1273,14 +1304,15 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { + private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void, implicitActivationEventsHandler: (info: IImplicitActivationEvents) => void): void { const users: IExtensionPointUser[] = []; for (const desc of availableExtensions) { if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { users.push({ description: desc, value: desc.contributes[extensionPoint.name as keyof typeof desc.contributes] as T, - collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) + collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name), + implicitActivationEventsCollector: new ExtensionActivationEventsCollector(implicitActivationEventsHandler, desc), }); } } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index ef4924a0ef1..bf75692d3b6 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -40,6 +40,11 @@ export interface IMessage { extensionPointId: string; } +export interface IImplicitActivationEvents { + implicitActivationEvents: string[]; + extensionId: ExtensionIdentifier; +} + export class LocalProcessRunningLocation { public readonly kind = ExtensionHostKind.LocalProcess; constructor( diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index be68971f7a3..21a449a51d8 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -10,7 +10,7 @@ import Severity from 'vs/base/common/severity'; import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IMessage } from 'vs/workbench/services/extensions/common/extensions'; +import { IImplicitActivationEvents, IMessage } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { ExtensionKind } from 'vs/platform/environment/common/environment'; import { allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; @@ -56,10 +56,31 @@ export class ExtensionMessageCollector { } } +export class ExtensionActivationEventsCollector { + private readonly _implicitActivationEventsHandler: (info: IImplicitActivationEvents) => void; + private readonly _extension: IExtensionDescription; + + constructor( + implicitActivationEventsHandler: (info: IImplicitActivationEvents) => void, + extension: IExtensionDescription + ) { + this._implicitActivationEventsHandler = implicitActivationEventsHandler; + this._extension = extension; + } + + public addImplicitActivationEvents(events: string[]) { + this._implicitActivationEventsHandler({ + implicitActivationEvents: events, + extensionId: this._extension.identifier, + }); + } +} + export interface IExtensionPointUser { description: IExtensionDescription; value: T; collector: ExtensionMessageCollector; + implicitActivationEventsCollector: ExtensionActivationEventsCollector; } export type IExtensionPointHandler = (extensions: readonly IExtensionPointUser[], delta: ExtensionPointUserDelta) => void; From 37243cb29a153721deb8cdaf895749d70f36cb6c Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 8 Nov 2022 08:32:19 -0800 Subject: [PATCH 02/67] Adopt implicit activation event collector for extensions contributing notebook serializers Ref https://github.com/microsoft/vscode/issues/134127 --- .../browser/services/notebookServiceImpl.ts | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 4242f322651..5f9fe346c17 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -100,7 +100,9 @@ export class NotebookProviderInfoStore extends Disposable { builtinProvidersFromCache.set(builtin.id, this.add(builtin)); }); + for (const extension of extensions) { + const implicitActivationEvents: string[] = []; for (const notebookContribution of extension.value) { if (!notebookContribution.type) { @@ -114,6 +116,7 @@ export class NotebookProviderInfoStore extends Disposable { if (!existing.extension && extension.description.isBuiltin && builtins.find(builtin => builtin.id === notebookContribution.type)) { // we are registering an extension which is using the same view type which is already cached builtinProvidersFromCache.get(notebookContribution.type)?.dispose(); + implicitActivationEvents.push(`onNotebookSerializer:${notebookContribution.type}`); } else { extension.collector.error(`Notebook type '${notebookContribution.type}' already used`); continue; @@ -129,7 +132,9 @@ export class NotebookProviderInfoStore extends Disposable { providerDisplayName: extension.description.displayName ?? extension.description.identifier.value, exclusive: false })); + } + extension.implicitActivationEventsCollector.addImplicitActivationEvents(implicitActivationEvents); } const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); @@ -606,20 +611,7 @@ export class NotebookService extends Disposable implements INotebookService { } await this._extensionService.whenInstalledExtensionsRegistered(); - - const info = this._notebookProviderInfoStore?.get(viewType); - const waitFor: Promise[] = [Event.toPromise(Event.filter(this.onAddViewType, () => { - return this._notebookProviders.has(viewType); - }))]; - - if (info && info.extension) { - const extensionManifest = await this._extensionService.getExtension(info.extension.value); - if (extensionManifest?.activationEvents && extensionManifest.activationEvents.indexOf(`onNotebook:${viewType}`) >= 0) { - waitFor.push(this._extensionService._activateById(info.extension, { startup: false, activationEvent: `onNotebook:${viewType}}`, extensionId: info.extension })); - } - } - - await Promise.race(waitFor); + await this._extensionService.activateByEvent(`onNotebookSerializer:${viewType}`); return this._notebookProviders.has(viewType); } From 503e4bc08d4406caf187cb870b341e0dfc5d85a4 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 8 Nov 2022 08:32:50 -0800 Subject: [PATCH 03/67] Adopt implicit activation event collector for commands --- src/vs/workbench/services/actions/common/menusExtensionPoint.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 4e2614f007c..6836bdb85fe 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -662,6 +662,8 @@ commandsExtensionPoint.setHandler(extensions => { precondition: ContextKeyExpr.deserialize(enablement), icon: absoluteIcon })); + + extension.implicitActivationEventsCollector.addImplicitActivationEvents([`onCommand:${command}`]); } // remove all previous command registrations From c6f126b3a1c80f2c338aa916a42379a424a454c8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 8 Nov 2022 09:37:55 -0800 Subject: [PATCH 04/67] Fix notebook serializer activation --- .../contrib/notebook/browser/services/notebookServiceImpl.ts | 3 ++- .../services/extensions/common/abstractExtensionService.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 5f9fe346c17..af780d4a2ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -116,13 +116,14 @@ export class NotebookProviderInfoStore extends Disposable { if (!existing.extension && extension.description.isBuiltin && builtins.find(builtin => builtin.id === notebookContribution.type)) { // we are registering an extension which is using the same view type which is already cached builtinProvidersFromCache.get(notebookContribution.type)?.dispose(); - implicitActivationEvents.push(`onNotebookSerializer:${notebookContribution.type}`); } else { extension.collector.error(`Notebook type '${notebookContribution.type}' already used`); continue; } } + implicitActivationEvents.push(`onNotebookSerializer:${notebookContribution.type}`); + this.add(new NotebookProviderInfo({ extension: extension.description.identifier, id: notebookContribution.type, diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 9502906a866..6c7fbc89149 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -1226,6 +1226,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } this._combineImplicitExtensionActivationEvents(); perf.mark('code/didHandleExtensionPoints'); + this._registry.set(availableExtensions); } private _combineImplicitExtensionActivationEvents() { From c783d52014102fefda660529cbb50297b1983714 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 11 Nov 2022 20:30:51 +0100 Subject: [PATCH 05/67] Add implicit activation events when scanning extensions --- .../common/extensionsScannerService.ts | 2 + .../common/implicitActivationEvents.ts | 46 +++++++++++++++++++ .../platform/extensions/common/extensions.ts | 3 +- .../browser/notebookExtensionPoint.ts | 9 +++- .../browser/services/notebookServiceImpl.ts | 6 --- .../contrib/terminal/common/terminal.ts | 4 +- .../actions/common/menusExtensionPoint.ts | 11 +++-- .../builtinExtensionsScannerService.ts | 2 + .../browser/webExtensionsScannerService.ts | 3 ++ .../common/abstractExtensionService.ts | 43 ++--------------- .../services/extensions/common/extensions.ts | 12 ----- .../extensions/common/extensionsRegistry.ts | 38 ++++++--------- 12 files changed, 91 insertions(+), 88 deletions(-) create mode 100644 src/vs/platform/extensionManagement/common/implicitActivationEvents.ts diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 13dfbaa1a07..a333a32d3e6 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -36,6 +36,7 @@ import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/p import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; +import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; export type IScannedExtensionManifest = IRelaxedExtensionManifest & { __metadata?: Metadata }; @@ -577,6 +578,7 @@ class ExtensionsScanner extends Disposable { const identifier = metadata?.id ? { id, uuid: metadata.id } : { id }; const type = metadata?.isSystem ? ExtensionType.System : input.type; const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin; + ImplicitActivationEvents.updateManifest(manifest); manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input)); const extension = { type, diff --git a/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts b/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts new file mode 100644 index 00000000000..8810abc6127 --- /dev/null +++ b/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; + +export interface IActivationEventsGenerator { + (contribution: T, result: { push(item: string): void }): void; +} + +export class ImplicitActivationEventsImpl { + + private readonly _generators = new Map>(); + + public register(extensionPointName: string, generator: IActivationEventsGenerator): void { + this._generators.set(extensionPointName, generator); + } + + public updateManifest(manifest: IExtensionManifest) { + if (!Array.isArray(manifest.activationEvents) || !manifest.contributes) { + return; + } + if (typeof manifest.main === 'undefined' && typeof manifest.browser === 'undefined') { + return; + } + + for (const extPointName in manifest.contributes) { + const generator = this._generators.get(extPointName); + if (!generator) { + // There's no generator for this extension point + continue; + } + const contrib = (manifest.contributes as any)[extPointName]; + const contribArr = Array.isArray(contrib) ? contrib : [contrib]; + try { + generator(contribArr, manifest.activationEvents); + } catch (err) { + onUnexpectedError(err); + } + } + } +} + +export const ImplicitActivationEvents: ImplicitActivationEventsImpl = new ImplicitActivationEventsImpl(); diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index b0b7687789c..b24736282a4 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -399,8 +399,7 @@ export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest browserNlsBundleUris?: { [language: string]: URI }; } -type MutableByKey = Pick & Readonly>; -export type IExtensionDescription = MutableByKey; +export type IExtensionDescription = Readonly; export function isApplicationScopedExtension(manifest: IExtensionManifest): boolean { return isLanguagePackExtension(manifest); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts index d720fa8bd9f..760f24f2c00 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts @@ -232,7 +232,14 @@ const notebookPreloadContribution: IJSONSchema = { export const notebooksExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'notebooks', - jsonSchema: notebookProviderContribution + jsonSchema: notebookProviderContribution, + activationEventsGenerator: (contribs: INotebookEditorContribution[], result: { push(item: string): void }) => { + for (const contrib of contribs) { + if (contrib.type) { + result.push(`onNotebookSerializer:${contrib.type}`); + } + } + } }); export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index af780d4a2ce..c6f592fceb7 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -100,9 +100,7 @@ export class NotebookProviderInfoStore extends Disposable { builtinProvidersFromCache.set(builtin.id, this.add(builtin)); }); - for (const extension of extensions) { - const implicitActivationEvents: string[] = []; for (const notebookContribution of extension.value) { if (!notebookContribution.type) { @@ -122,8 +120,6 @@ export class NotebookProviderInfoStore extends Disposable { } } - implicitActivationEvents.push(`onNotebookSerializer:${notebookContribution.type}`); - this.add(new NotebookProviderInfo({ extension: extension.description.identifier, id: notebookContribution.type, @@ -133,9 +129,7 @@ export class NotebookProviderInfoStore extends Disposable { providerDisplayName: extension.description.displayName ?? extension.description.identifier.value, exclusive: false })); - } - extension.implicitActivationEventsCollector.addImplicitActivationEvents(implicitActivationEvents); } const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 7b05453c1c3..8eb8ceb9346 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, ITerminalProcessOptions, ITerminalContributions } from 'vs/platform/terminal/common/terminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -710,7 +710,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.toggleMaximizedPanel' ]; -export const terminalContributionsDescriptor: IExtensionPointDescriptor = { +export const terminalContributionsDescriptor: IExtensionPointDescriptor = { extensionPoint: 'terminal', defaultExtensionKind: ['workspace'], jsonSchema: { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 6836bdb85fe..80a2cb98aa2 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -623,7 +623,14 @@ const _commandRegistrations = new DisposableStore(); export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'commands', - jsonSchema: schema.commandsContribution + jsonSchema: schema.commandsContribution, + activationEventsGenerator: (contribs: schema.IUserFriendlyCommand[], result: { push(item: string): void }) => { + for (const contrib of contribs) { + if (contrib.command) { + result.push(`onCommand:${contrib.command}`); + } + } + } }); commandsExtensionPoint.setHandler(extensions => { @@ -662,8 +669,6 @@ commandsExtensionPoint.setHandler(extensions => { precondition: ContextKeyExpr.deserialize(enablement), icon: absoluteIcon })); - - extension.implicitActivationEventsCollector.addImplicitActivationEvents([`onCommand:${command}`]); } // remove all previous command registrations diff --git a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts index 7e797a7bc1d..21a1cd4c3dc 100644 --- a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts @@ -15,6 +15,7 @@ import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLo import { IProductService } from 'vs/platform/product/common/productService'; import { ITranslations, localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { ILogService } from 'vs/platform/log/common/log'; +import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; interface IBundledExtension { extensionPath: string; @@ -74,6 +75,7 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne } browserNlsBundleUris.en = uriIdentityService.extUri.resolvePath(builtinExtensionsServiceUrl!, e.browserNlsMetadataPath); } + ImplicitActivationEvents.updateManifest(e.packageJSON); return { identifier: { id }, location: uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.extensionPath), diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index abf7fbbcd96..36a5053ad4b 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -43,6 +43,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; type GalleryExtensionInfo = { readonly id: string; preRelease?: boolean; migrateStorageFrom?: string }; type ExtensionInfo = { readonly id: string; preRelease: boolean }; @@ -679,6 +680,8 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten }; } + ImplicitActivationEvents.updateManifest(manifest); + const packageNLSUri = webExtension.packageNLSUris?.get(Language.value().toLowerCase()); if (packageNLSUri || webExtension.fallbackPackageNLSUri) { manifest = packageNLSUri diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 6c7fbc89149..053cc45f822 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -15,8 +15,8 @@ import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementServ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, IImplicitActivationEvents } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionActivationEventsCollector, ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; import { createExtensionHostManager, IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -153,7 +153,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private readonly _installedExtensionsReady: Barrier; private readonly _isDev: boolean; private readonly _extensionsMessages: Map; - private readonly _extensionsImplicitActivationEvents: Map>; private readonly _allRequestedActivateEvents = new Set(); private readonly _proposedApiController: ProposedApiController; private readonly _isExtensionDevHost: boolean; @@ -207,7 +206,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._installedExtensionsReady = new Barrier(); this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; this._extensionsMessages = new Map(); - this._extensionsImplicitActivationEvents = new Map>(); this._proposedApiController = _instantiationService.createInstance(ProposedApiController); this._extensionHostManagers = []; @@ -1213,47 +1211,17 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - const implicitActivationEventsHandler = (info: IImplicitActivationEvents) => this._handleImplicitExtensionActivationEvents(info); const availableExtensions = this._registry.getAllExtensionDescriptions(); const extensionPoints = ExtensionsRegistry.getExtensionPoints(); perf.mark('code/willHandleExtensionPoints'); for (const extensionPoint of extensionPoints) { if (affectedExtensionPoints[extensionPoint.name]) { perf.mark(`code/willHandleExtensionPoint/${extensionPoint.name}`); - AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler, implicitActivationEventsHandler); + AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler); perf.mark(`code/didHandleExtensionPoint/${extensionPoint.name}`); } } - this._combineImplicitExtensionActivationEvents(); perf.mark('code/didHandleExtensionPoints'); - this._registry.set(availableExtensions); - } - - private _combineImplicitExtensionActivationEvents() { - for (const [extensionKey, implicitActivationEvents] of this._extensionsImplicitActivationEvents.entries()) { - const extensionDescription = this._registry.getExtensionDescription(extensionKey); - if (!extensionDescription) { - continue; - } - - for (const activationEvents of (extensionDescription?.activationEvents ?? [])) { - implicitActivationEvents.add(activationEvents); - } - extensionDescription.activationEvents = Array.from(implicitActivationEvents); - } - } - - private _handleImplicitExtensionActivationEvents(extensionActivationEventsInfo: IImplicitActivationEvents) { - const extensionKey = ExtensionIdentifier.toKey(extensionActivationEventsInfo.extensionId); - - if (!this._extensionsImplicitActivationEvents.has(extensionKey)) { - this._extensionsImplicitActivationEvents.set(extensionKey, new Set()); - } - const implicitActivationEvents = this._extensionsImplicitActivationEvents.get(extensionKey); - - for (const activationEvent of extensionActivationEventsInfo.implicitActivationEvents) { - implicitActivationEvents?.add(activationEvent); - } } private _handleExtensionPointMessage(msg: IMessage) { @@ -1305,15 +1273,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void, implicitActivationEventsHandler: (info: IImplicitActivationEvents) => void): void { + private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { const users: IExtensionPointUser[] = []; for (const desc of availableExtensions) { if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { users.push({ description: desc, value: desc.contributes[extensionPoint.name as keyof typeof desc.contributes] as T, - collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name), - implicitActivationEventsCollector: new ExtensionActivationEventsCollector(implicitActivationEventsHandler, desc), + collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) }); } } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index bf75692d3b6..a524523645b 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -40,11 +40,6 @@ export interface IMessage { extensionPointId: string; } -export interface IImplicitActivationEvents { - implicitActivationEvents: string[]; - extensionId: ExtensionIdentifier; -} - export class LocalProcessRunningLocation { public readonly kind = ExtensionHostKind.LocalProcess; constructor( @@ -561,12 +556,6 @@ export interface IExtensionService { * @param env New properties for the remote extension host */ setRemoteEnvironment(env: { [key: string]: string | null }): Promise; - - /** - * Please do not use! - * (This is public such that the extension host process can coordinate with and call back in the IExtensionService) - */ - _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; } export interface IInternalExtensionService { @@ -631,5 +620,4 @@ export class NullExtensionService implements IExtensionService { async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { } canAddExtension(): boolean { return false; } canRemoveExtension(): boolean { return false; } - _activateById(_extensionId: ExtensionIdentifier, _reason: ExtensionActivationReason): Promise { return Promise.resolve(); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 21a449a51d8..fba09010b02 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -10,11 +10,12 @@ import Severity from 'vs/base/common/severity'; import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IImplicitActivationEvents, IMessage } from 'vs/workbench/services/extensions/common/extensions'; +import { IMessage } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { ExtensionKind } from 'vs/platform/environment/common/environment'; import { allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { productSchemaId } from 'vs/platform/product/common/productService'; +import { ImplicitActivationEvents, IActivationEventsGenerator } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; const schemaRegistry = Registry.as(Extensions.JSONContribution); @@ -56,31 +57,10 @@ export class ExtensionMessageCollector { } } -export class ExtensionActivationEventsCollector { - private readonly _implicitActivationEventsHandler: (info: IImplicitActivationEvents) => void; - private readonly _extension: IExtensionDescription; - - constructor( - implicitActivationEventsHandler: (info: IImplicitActivationEvents) => void, - extension: IExtensionDescription - ) { - this._implicitActivationEventsHandler = implicitActivationEventsHandler; - this._extension = extension; - } - - public addImplicitActivationEvents(events: string[]) { - this._implicitActivationEventsHandler({ - implicitActivationEvents: events, - extensionId: this._extension.identifier, - }); - } -} - export interface IExtensionPointUser { description: IExtensionDescription; value: T; collector: ExtensionMessageCollector; - implicitActivationEventsCollector: ExtensionActivationEventsCollector; } export type IExtensionPointHandler = (extensions: readonly IExtensionPointUser[], delta: ExtensionPointUserDelta) => void; @@ -580,23 +560,33 @@ export const schema: IJSONSchema = { } }; -export interface IExtensionPointDescriptor { +export type toArray = T extends Array ? T : T[]; + +export interface IExtensionPointDescriptor { extensionPoint: string; deps?: IExtensionPoint[]; jsonSchema: IJSONSchema; defaultExtensionKind?: ExtensionKind[]; + /** + * A function which runs before the extension point has been validated and which + * can should collect automatic activation events from the contribution. + */ + activationEventsGenerator?: IActivationEventsGenerator>; } export class ExtensionsRegistryImpl { private readonly _extensionPoints = new Map>(); - public registerExtensionPoint(desc: IExtensionPointDescriptor): IExtensionPoint { + public registerExtensionPoint(desc: IExtensionPointDescriptor): IExtensionPoint { if (this._extensionPoints.has(desc.extensionPoint)) { throw new Error('Duplicate extension point: ' + desc.extensionPoint); } const result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); this._extensionPoints.set(desc.extensionPoint, result); + if (desc.activationEventsGenerator) { + ImplicitActivationEvents.register(desc.extensionPoint, desc.activationEventsGenerator); + } schema.properties!['contributes'].properties![desc.extensionPoint] = desc.jsonSchema; schemaRegistry.registerSchema(schemaId, schema); From 77accd5b3fe4e32fd41bd9f938f9994743657eec Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 15 Nov 2022 23:25:13 +0000 Subject: [PATCH 06/67] Intercept remote extension scanning on client side --- .../remote/common/abstractRemoteAgentService.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 9f0841a6310..884d1d28ef7 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -20,6 +20,7 @@ import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/tel 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'; export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService { @@ -85,14 +86,24 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I scanExtensions(skipExtensions: ExtensionIdentifier[] = []): Promise { return this._withChannel( - (channel, connection) => RemoteExtensionEnvironmentChannelClient.scanExtensions(channel, connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI, skipExtensions), + 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( - (channel, connection) => RemoteExtensionEnvironmentChannelClient.scanSingleExtension(channel, connection.remoteAuthority, isBuiltin, extensionLocation), + 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); } From 1c37752e06c4a0718d72bc454ec3df57d53248c4 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 16 Nov 2022 13:37:51 +0000 Subject: [PATCH 07/67] =?UTF-8?q?=F0=9F=94=A8=20Change=20localized=20text?= =?UTF-8?q?=20ID=20for=20`breadcrumbs.focusAndSelect`=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 92542a8d03f..799fa3218ce 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -549,8 +549,8 @@ registerAction2(class FocusAndSelectBreadcrumbs extends Action2 { super({ id: 'breadcrumbs.focusAndSelect', title: { - value: localize('cmd.focus', "Focus Breadcrumbs"), - original: 'Focus Breadcrumbs' + value: localize('cmd.focusAndSelect', "Focus and Select Breadcrumbs"), + original: 'Focus and Select Breadcrumbs' }, precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, keybinding: { From 6283ef38062356c3e2f2b620d5004a44c4a73a99 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 16 Nov 2022 13:39:26 +0000 Subject: [PATCH 08/67] =?UTF-8?q?=F0=9F=94=A8=20Register=20`breadcrumbs.fo?= =?UTF-8?q?cus`=20command=20via=20`registerAction2`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- .../parts/editor/breadcrumbsControl.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 799fa3218ce..5079596afe5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -573,6 +573,27 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => focusAndSelectHandler(accessor, false) }); +registerAction2(class FocusBreadcrumbs extends Action2 { + constructor() { + super({ + id: 'breadcrumbs.focus', + title: { + value: localize('cmd.focus', "Focus Breadcrumbs"), + original: 'Focus Breadcrumbs' + }, + precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Semicolon, + when: BreadcrumbsControl.CK_BreadcrumbsPossible, + } + }); + } + run(accessor: ServicesAccessor, ...args: any[]): void { + focusAndSelectHandler(accessor, false); + } +}); + // this commands is only enabled when breadcrumbs are // disabled which it then enables and focuses KeybindingsRegistry.registerCommandAndKeybindingRule({ From aa96ef760a56282fb7534f81f9278704f937604f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 16 Nov 2022 13:40:31 +0000 Subject: [PATCH 09/67] =?UTF-8?q?=F0=9F=96=BC=20Display=20`breadcrumbs.foc?= =?UTF-8?q?us`=20command=20on=20the=20palette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 5079596afe5..99565340c5b 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -586,7 +586,8 @@ registerAction2(class FocusBreadcrumbs extends Action2 { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Semicolon, when: BreadcrumbsControl.CK_BreadcrumbsPossible, - } + }, + f1: true }); } run(accessor: ServicesAccessor, ...args: any[]): void { From a61a96ba49b5455b40c09ece2e80c531726363ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 16 Nov 2022 11:13:48 -0800 Subject: [PATCH 10/67] Fix casing of action widget hover Fixes #166496 --- src/vs/platform/actionWidget/browser/actionList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 5dacc07d2f7..f040e6c997f 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -127,7 +127,7 @@ class ActionItemRenderer> implements IListR if (element.disabled) { data.container.title = element.label; } else if (actionTitle && previewTitle) { - data.container.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", actionTitle, previewTitle); + data.container.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to apply, Shift+F2 to preview"'] }, "{0} to apply, {1} to preview", actionTitle, previewTitle); } else { data.container.title = ''; } From 6e3dbd4a91029db6cc9f50a9514431b67473d7eb Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 16 Nov 2022 14:20:12 -0500 Subject: [PATCH 11/67] Bump distro (#166494) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6bbff98b9ef..c2529db54dd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.74.0", - "distro": "d7139fbecd8c263fbe78f29e5cebc53f220e10b2", + "distro": "7153817c51ee07fd57e84a33afdaede63b3937f8", "author": { "name": "Microsoft Corporation" }, From 8deed65f643599418985b13303fac46eafab92fe Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 16 Nov 2022 14:41:04 -0500 Subject: [PATCH 12/67] Allow the ability to opt out of cleaning (#166136) * Add the ability to opt out of data cleaning * Comment * Update to Joh's suggested solution --- .../telemetry/common/serverTelemetryService.ts | 8 ++++---- src/vs/platform/telemetry/common/telemetry.ts | 4 ++-- .../platform/telemetry/common/telemetryService.ts | 12 ++++++------ src/vs/platform/telemetry/common/telemetryUtils.ts | 14 ++++++++++++++ .../test/browser/telemetryService.test.ts | 4 ++-- .../browser/parts/editor/editorGroupView.ts | 3 ++- .../telemetry/browser/telemetry.contribution.ts | 6 +++--- .../extensions/common/extensionHostManager.ts | 2 +- .../services/telemetry/browser/telemetryService.ts | 8 ++++---- .../telemetry/electron-sandbox/telemetryService.ts | 8 ++++---- 10 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/vs/platform/telemetry/common/serverTelemetryService.ts b/src/vs/platform/telemetry/common/serverTelemetryService.ts index a034f4d81c0..dd9fd5359ed 100644 --- a/src/vs/platform/telemetry/common/serverTelemetryService.ts +++ b/src/vs/platform/telemetry/common/serverTelemetryService.ts @@ -30,15 +30,15 @@ export class ServerTelemetryService extends TelemetryService implements IServerT this._injectedTelemetryLevel = injectedTelemetryLevel; } - override publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { + override publicLog(eventName: string, data?: ITelemetryData): Promise { if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) { return Promise.resolve(undefined); } - return super.publicLog(eventName, data, anonymizeFilePaths); + return super.publicLog(eventName, data); } - override publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise { - return this.publicLog(eventName, data as ITelemetryData | undefined, anonymizeFilePaths); + override publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): Promise { + return this.publicLog(eventName, data as ITelemetryData | undefined); } override publicLogError(errorEventName: string, data?: ITelemetryData): Promise { diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index f465690a677..e2817ecb35d 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -34,13 +34,13 @@ export interface ITelemetryService { /** * @deprecated Use publicLog2 and the typescript GDPR annotation where possible */ - publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise; + publicLog(eventName: string, data?: ITelemetryData): Promise; /** * Sends a telemetry event that has been privacy approved. * Do not call this unless you have been given approval. */ - publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise; + publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): Promise; /** * @deprecated Use publicLogError2 and the typescript GDPR annotation where possible diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index fef48e29682..2936fba9263 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -102,7 +102,7 @@ export class TelemetryService implements ITelemetryService { this._disposables.dispose(); } - private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { + private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData): Promise { // don't send events when the user is optout if (this.telemetryLevel.value < eventLevel) { return Promise.resolve(undefined); @@ -128,12 +128,12 @@ export class TelemetryService implements ITelemetryService { }); } - publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { - return this._log(eventName, TelemetryLevel.USAGE, data, anonymizeFilePaths); + publicLog(eventName: string, data?: ITelemetryData): Promise { + return this._log(eventName, TelemetryLevel.USAGE, data); } - publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise { - return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); + publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): Promise { + return this.publicLog(eventName, data as ITelemetryData); } publicLogError(errorEventName: string, data?: ITelemetryData): Promise { @@ -142,7 +142,7 @@ export class TelemetryService implements ITelemetryService { } // Send error event and anonymize paths - return this._log(errorEventName, TelemetryLevel.ERROR, data, true); + return this._log(errorEventName, TelemetryLevel.ERROR, data); } publicLogError2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): Promise { diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 1b02638b6a5..88f56ebe955 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -15,6 +15,14 @@ import { verifyMicrosoftInternalDomain } from 'vs/platform/telemetry/common/comm import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ICustomEndpointTelemetryService, ITelemetryData, ITelemetryEndpoint, ITelemetryInfo, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; +/** + * A special class used to denoate a telemetry value which should not be clean. + * This is because that value is "Trusted" not to contain identifiable information such as paths + */ +export class TrustedTelemetryValue { + constructor(public readonly value: any) { } +} + export class NullTelemetryServiceShape implements ITelemetryService { declare readonly _serviceBrand: undefined; readonly sendErrorTelemetry = false; @@ -399,6 +407,12 @@ function removePropertiesWithPossibleUserInfo(property: string): string { */ export function cleanData(data: Record, cleanUpPatterns: RegExp[]): Record { return cloneAndChange(data, value => { + + // If it's a trusted value it means it's okay to skip cleaning so we don't clean it + if (value instanceof TrustedTelemetryValue) { + return value.value; + } + // We only know how to clean strings if (typeof value === 'string') { let updatedProperty = value; diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index 501ea625958..26f261d253e 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -224,8 +224,8 @@ suite('TelemetryService', () => { return Promise.all(this.promises); } - override publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { - const p = super.publicLog(eventName, data, anonymizeFilePaths); + override publicLog(eventName: string, data?: ITelemetryData): Promise { + const p = super.publicLog(eventName, data); // publicLog is called from the ctor and therefore promises can be undefined this.promises = this.promises ?? []; this.promises.push(p); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0c2c8f23796..a33e41cc050 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -53,6 +53,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { isLinux, isMacintosh, isNative, isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { TrustedTelemetryValue } from 'vs/platform/telemetry/common/telemetryUtils'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -642,7 +643,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Remove query parameters from the resource extension const queryStringLocation = resourceExt.indexOf('?'); resourceExt = queryStringLocation !== -1 ? resourceExt.substr(0, queryStringLocation) : resourceExt; - descriptor['resource'] = { mimeType: getMimeTypes(resource).join(', '), scheme: resource.scheme, ext: resourceExt, path: hash(path) }; + descriptor['resource'] = { mimeType: new TrustedTelemetryValue(getMimeTypes(resource).join(', ')), scheme: resource.scheme, ext: resourceExt, path: hash(path) }; /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index f44c01d97a7..f1ea431aa9b 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -15,7 +15,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { language } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; -import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { configurationTelemetry, TrustedTelemetryValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService, ITextFileSaveEvent, ITextFileResolveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { extname, basename, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -28,7 +28,7 @@ import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; type TelemetryData = { - mimeType: string; + mimeType: TrustedTelemetryValue; ext: string; path: number; reason?: number; @@ -216,7 +216,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr const fileName = basename(resource); const path = resource.scheme === Schemas.file ? resource.fsPath : resource.path; const telemetryData = { - mimeType: getMimeTypes(resource).join(', '), + mimeType: new TrustedTelemetryValue(getMimeTypes(resource).join(', ')), ext, path: hash(path), reason, diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index bfbc03d4f9c..6dd0f2be6df 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -165,7 +165,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { if (err && err.stack) { failureTelemetryEvent.errorStack = err.stack; } - this._telemetryService.publicLog2('extensionHostStartup', failureTelemetryEvent, true); + this._telemetryService.publicLog2('extensionHostStartup', failureTelemetryEvent); return null; } diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 6d5739aed97..80493e185ae 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -87,12 +87,12 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.telemetryLevel; } - publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { - return this.impl.publicLog(eventName, data, anonymizeFilePaths); + publicLog(eventName: string, data?: ITelemetryData): Promise { + return this.impl.publicLog(eventName, data); } - publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean) { - return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); + publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as ITelemetryData); } publicLogError(errorEventName: string, data?: ITelemetryData): Promise { diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts index 342e4f61afb..9d19cd13d3b 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts @@ -62,12 +62,12 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.telemetryLevel; } - publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { - return this.impl.publicLog(eventName, data, anonymizeFilePaths); + publicLog(eventName: string, data?: ITelemetryData): Promise { + return this.impl.publicLog(eventName, data); } - publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean) { - return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); + publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as ITelemetryData); } publicLogError(errorEventName: string, data?: ITelemetryData): Promise { From a60b513c458e26a31262ca2297b1f2a54cc45a12 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 16 Nov 2022 11:44:45 -0800 Subject: [PATCH 13/67] fix cleaning on windows (#166498) --- src/vs/platform/telemetry/common/telemetryService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 2936fba9263..a6668775118 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -58,6 +58,10 @@ export class TelemetryService implements ITelemetryService { for (const piiPath of this._piiPaths) { this._cleanupPatterns.push(new RegExp(escapeRegExpCharacters(piiPath), 'gi')); + + if (piiPath.indexOf('\\') >= 0) { + this._cleanupPatterns.push(new RegExp(escapeRegExpCharacters(piiPath.replace(/\\/g, '/')), 'gi')); + } } this._updateTelemetryLevel(); From 2deaa43aa626a5b2914c49fe647e4ac87ff3f5f3 Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 16 Nov 2022 14:01:31 -0800 Subject: [PATCH 14/67] specify notebook document --- .../src/singlefolder-tests/interactiveWindow.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 181068b38f6..4f8a1211c2d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -36,7 +36,7 @@ async function addCell(code: string, notebook: vscode.NotebookDocument) { async function addCellAndRun(code: string, notebook: vscode.NotebookDocument, i: number) { const cell = await addCell(code, notebook); const event = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.execute', { start: i, end: i + 1 }); + await vscode.commands.executeCommand('notebook.cell.execute', { start: i, end: i + 1 }, notebook.uri); await event; assert.strictEqual(cell.outputs.length, 1, 'execute failed'); return cell; From a1d42637be84120a19acd4515331bc7e1c5a6c91 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 16 Nov 2022 17:07:38 -0500 Subject: [PATCH 15/67] Exempt perf markers from telemetry cleaning (#166508) --- src/vs/workbench/services/timer/browser/timerService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index 2112d663b80..9b81da873fe 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -17,6 +17,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { TrustedTelemetryValue } from 'vs/platform/telemetry/common/telemetryUtils'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -583,7 +584,7 @@ export abstract class AbstractTimerService implements ITimerService { // event and it is "normalized" to a relative timestamp where the first mark // defines the start - type Mark = { source: string; name: string; startTime: number }; + type Mark = { source: string; name: TrustedTelemetryValue; startTime: number }; type MarkClassification = { owner: 'jrieken'; comment: 'Information about a performance marker'; @@ -595,7 +596,7 @@ export abstract class AbstractTimerService implements ITimerService { for (const mark of marks) { this._telemetryService.publicLog2('startup.timer.mark', { source, - name: mark.name, + name: new TrustedTelemetryValue(mark.name), startTime: mark.startTime }); } From 707f91aaa68a6fa48b09636678edf3b3bcc3454f Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 16 Nov 2022 14:10:49 -0800 Subject: [PATCH 16/67] cli: fix 'failed to lookup tunnel' with a name given (#166507) Fixes https://github.com/microsoft/vscode-cli/issues/563 If a new tunnel `--name` was provided but the existing tunnel was deleted, the CLI would error out with a lookup failed message. Instead it now recreates it like it normally would. --- cli/src/tunnels/dev_tunnels.rs | 151 ++++++++++++++++++++------------- cli/src/util/zipper.rs | 11 +-- 2 files changed, 97 insertions(+), 65 deletions(-) diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index eb362ef8e5b..911bf547e2f 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -12,6 +12,7 @@ use crate::util::input::prompt_placeholder; use crate::{debug, info, log, spanf, trace, warning}; use async_trait::async_trait; use futures::TryFutureExt; +use lazy_static::lazy_static; use rand::prelude::IteratorRandom; use regex::Regex; use reqwest::StatusCode; @@ -121,7 +122,7 @@ impl AccessTokenProvider for LookupAccessTokenProvider { match tunnel_lookup { Ok(tunnel) => Ok(get_host_token_from_tunnel(&tunnel)), - Err(e) => Err(wrap(e, "failed to lookup tunnel")), + Err(e) => Err(wrap(e, "failed to lookup tunnel for host token")), } } } @@ -212,6 +213,14 @@ fn is_valid_name(name: &str) -> Result<(), InvalidTunnelName> { Ok(()) } +lazy_static! { + static ref HOST_TUNNEL_REQUEST_OPTIONS: TunnelRequestOptions = TunnelRequestOptions { + include_ports: true, + token_scopes: vec!["host".to_string()], + ..Default::default() + }; +} + /// Structure optionally passed into `start_existing_tunnel` to forward an existing tunnel. #[derive(Clone, Debug)] pub struct ExistingTunnel { @@ -260,6 +269,7 @@ impl DevTunnels { Ok(()) } + /// Renames the current tunnel to the new name. pub async fn rename_tunnel(&mut self, name: &str) -> Result<(), AnyError> { is_valid_name(name)?; @@ -269,7 +279,7 @@ impl DevTunnels { Some(t) => t, None => { debug!(self.log, "No code server tunnel found, creating new one"); - let (persisted, _) = self.create_tunnel(name).await?; + let (persisted, _) = self.create_tunnel(name, NO_REQUEST_OPTIONS).await?; self.launcher_tunnel.save(Some(persisted))?; return Ok(()); } @@ -282,7 +292,7 @@ impl DevTunnels { self.log.span("dev-tunnel.tag.get"), self.client.get_tunnel(&locator, NO_REQUEST_OPTIONS) ) - .map_err(|e| wrap(e, "failed to lookup tunnel"))?; + .map_err(|e| wrap(e, "failed to lookup original tunnel"))?; full_tunnel.tags = vec![name.to_string(), VSCODE_CLI_TUNNEL_TAG.to_string()]; spanf!( @@ -297,6 +307,70 @@ impl DevTunnels { Ok(()) } + /// Updates the name of the existing persisted tunnel to the new name. + /// Gracefully creates a new tunnel if the previous one was deleted. + async fn update_tunnel_name( + &mut self, + persisted: PersistedTunnel, + name: &str, + ) -> Result<(Tunnel, PersistedTunnel), AnyError> { + self.check_is_name_free(name).await?; + + debug!(self.log, "Tunnel name changed, applying updates..."); + + let (mut full_tunnel, mut persisted, is_new) = self + .get_or_create_tunnel(persisted, Some(name), NO_REQUEST_OPTIONS) + .await?; + if is_new { + return Ok((full_tunnel, persisted)); + } + + full_tunnel.tags = vec![name.to_string(), VSCODE_CLI_TUNNEL_TAG.to_string()]; + + let new_tunnel = spanf!( + self.log, + self.log.span("dev-tunnel.tag.update"), + self.client.update_tunnel(&full_tunnel, NO_REQUEST_OPTIONS) + ) + .map_err(|e| wrap(e, "failed to rename tunnel"))?; + + persisted.name = name.to_string(); + self.launcher_tunnel.save(Some(persisted.clone()))?; + + Ok((new_tunnel, persisted)) + } + + /// Gets the persisted tunnel from the service, or creates a new one. + /// If `create_with_new_name` is given, the new tunnel has that name + /// instead of the one previously persisted. + async fn get_or_create_tunnel( + &mut self, + persisted: PersistedTunnel, + create_with_new_name: Option<&str>, + options: &TunnelRequestOptions, + ) -> Result<(Tunnel, PersistedTunnel, /* is_new */ bool), AnyError> { + let tunnel_lookup = spanf!( + self.log, + self.log.span("dev-tunnel.tag.get"), + self.client.get_tunnel(&persisted.locator(), options) + ); + + match tunnel_lookup { + Ok(ft) => Ok((ft, persisted, false)), + Err(HttpError::ResponseError(e)) + if e.status_code == StatusCode::NOT_FOUND + || e.status_code == StatusCode::FORBIDDEN => + { + let (persisted, tunnel) = self + .create_tunnel(create_with_new_name.unwrap_or(&persisted.name), options) + .await?; + self.launcher_tunnel.save(Some(persisted.clone()))?; + Ok((tunnel, persisted, true)) + } + Err(e) => Err(wrap(e, "failed to lookup tunnel").into()), + } + } + /// Starts a new tunnel for the code server on the port. Unlike `start_new_tunnel`, /// this attempts to reuse or create a tunnel of a preferred name or of a generated friendly tunnel name. pub async fn start_new_launcher_tunnel( @@ -308,64 +382,23 @@ impl DevTunnels { Some(mut persisted) => { if let Some(name) = preferred_name { if persisted.name.ne(&name) { - self.check_is_name_free(&name).await?; - let mut full_tunnel = spanf!( - self.log, - self.log.span("dev-tunnel.tag.get"), - self.client - .get_tunnel(&persisted.locator(), NO_REQUEST_OPTIONS) - ) - .map_err(|e| wrap(e, "failed to lookup tunnel"))?; - - info!(self.log, "Updating name of existing tunnel"); - - full_tunnel.tags = - vec![name.to_string(), VSCODE_CLI_TUNNEL_TAG.to_string()]; - if spanf!( - self.log, - self.log.span("dev-tunnel.tag.update"), - self.client.update_tunnel(&full_tunnel, NO_REQUEST_OPTIONS) - ) - .is_ok() - { - persisted.name = name.to_string(); - self.launcher_tunnel.save(Some(persisted.clone()))?; - } + (_, persisted) = self.update_tunnel_name(persisted, &name).await?; } } - let tunnel_lookup = spanf!( - self.log, - self.log.span("dev-tunnel.tag.get"), - self.client.get_tunnel( - &persisted.locator(), - &TunnelRequestOptions { - include_ports: true, - token_scopes: vec!["host".to_string()], - ..Default::default() - } - ) - ); - - match tunnel_lookup { - Ok(ft) => (ft, persisted), - Err(HttpError::ResponseError(e)) - if e.status_code == StatusCode::NOT_FOUND - || e.status_code == StatusCode::FORBIDDEN => - { - let (persisted, tunnel) = self.create_tunnel(&persisted.name).await?; - self.launcher_tunnel.save(Some(persisted.clone()))?; - (tunnel, persisted) - } - Err(e) => return Err(AnyError::from(wrap(e, "failed to lookup tunnel"))), - } + let (tunnel, persisted, _) = self + .get_or_create_tunnel(persisted, None, &HOST_TUNNEL_REQUEST_OPTIONS) + .await?; + (tunnel, persisted) } None => { debug!(self.log, "No code server tunnel found, creating new one"); let name = self .get_name_for_tunnel(preferred_name, use_random_name) .await?; - let (persisted, full_tunnel) = self.create_tunnel(&name).await?; + let (persisted, full_tunnel) = self + .create_tunnel(&name, &HOST_TUNNEL_REQUEST_OPTIONS) + .await?; self.launcher_tunnel.save(Some(persisted.clone()))?; (full_tunnel, persisted) } @@ -419,7 +452,11 @@ impl DevTunnels { .await } - async fn create_tunnel(&mut self, name: &str) -> Result<(PersistedTunnel, Tunnel), AnyError> { + async fn create_tunnel( + &mut self, + name: &str, + options: &TunnelRequestOptions, + ) -> Result<(PersistedTunnel, Tunnel), AnyError> { info!(self.log, "Creating tunnel with the name: {}", name); let mut tried_recycle = false; @@ -433,7 +470,7 @@ impl DevTunnels { let result = spanf!( self.log, self.log.span("dev-tunnel.create"), - self.client.create_tunnel(&new_tunnel, NO_REQUEST_OPTIONS) + self.client.create_tunnel(&new_tunnel, options) ); match result { @@ -446,9 +483,9 @@ impl DevTunnels { } return Err(AnyError::from(TunnelCreationFailed( - name.to_string(), - "You've exceeded the 10 machine limit for the port fowarding service. Please remove other machines before trying to add this machine.".to_string(), - ))); + name.to_string(), + "You've exceeded the 10 machine limit for the port fowarding service. Please remove other machines before trying to add this machine.".to_string(), + ))); } Err(e) => { return Err(AnyError::from(TunnelCreationFailed( diff --git a/cli/src/util/zipper.rs b/cli/src/util/zipper.rs index bfb7f4085b6..84eec040b0e 100644 --- a/cli/src/util/zipper.rs +++ b/cli/src/util/zipper.rs @@ -54,12 +54,7 @@ where let mut archive = zip::ZipArchive::new(file) .map_err(|e| wrap(e, format!("failed to open zip archive {}", path.display())))?; - let skip_segments_no = if should_skip_first_segment(&mut archive) { - 1 - } else { - 0 - }; - + let skip_segments_no = usize::from(should_skip_first_segment(&mut archive)); for i in 0..archive.len() { reporter.report_progress(i as u64, archive.len() as u64); let mut file = archive @@ -83,7 +78,7 @@ where } if let Some(p) = outpath.parent() { - fs::create_dir_all(&p) + fs::create_dir_all(p) .map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?; } @@ -138,7 +133,7 @@ fn apply_permissions(file: &ZipFile, outpath: &Path) -> Result<(), WrappedError> use std::os::unix::fs::PermissionsExt; if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).map_err(|e| { + fs::set_permissions(outpath, fs::Permissions::from_mode(mode)).map_err(|e| { wrap( e, format!("error setting permissions on {}", outpath.display()), From 838b48504cd9a2338e2ca9e854da9cec990c4d57 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 16 Nov 2022 14:16:36 -0800 Subject: [PATCH 17/67] Pick up TS 4.9 final (#166509) --- extensions/package.json | 2 +- .../typescript-language-features/src/extension.browser.ts | 2 +- extensions/yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index b9739529518..7c28cece4f9 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^4.9.2-rc" + "typescript": "^4.9.3" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts index 21bdad79300..7b37e470b3e 100644 --- a/extensions/typescript-language-features/src/extension.browser.ts +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -58,7 +58,7 @@ export function activate( new TypeScriptVersion( TypeScriptVersionSource.Bundled, vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(), - API.fromSimpleString('4.8.2'))); + API.fromSimpleString('4.9.3'))); let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 8cd7aaf26af..87d201a9541 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -175,10 +175,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -typescript@^4.9.2-rc: - version "4.9.2-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.2-rc.tgz#3525dbeb8458a8c98ce7d60724e4a9380d7b46e7" - integrity sha512-Ly9UUxJBfiiFjfegI1gsW9FI8Xhw1cuwRMBJ4wdYg+UXZR4VnZvD1OnBDj/iQ2U+tWbWEjYqJ5xx1Cwr4Vsa4w== +typescript@^4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" + integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== vscode-grammar-updater@^1.1.0: version "1.1.0" From 9318bcc5183dbbc49cea8843859cbdeb59b94eaf Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:37:44 -0800 Subject: [PATCH 18/67] Search Contributions Cleanup (#165538) Major cleanup on search contribs/actions, namely: - converting all `Action` and related registrations to `Action2` - Converting the `Actionbar` that appears on hover of a search result to `MenuWorkbenchToolBar` - Overall organization - Converting any `ICommandHandler` to functions if they don't need to be `ICommandHandler` - Adding titles to all search commands --- src/vs/platform/actions/common/actions.ts | 1 + .../search/browser/search.contribution.ts | 958 +----------------- .../contrib/search/browser/searchActions.ts | 778 -------------- .../search/browser/searchActionsBase.ts | 62 ++ .../search/browser/searchActionsCopy.ts | 256 +++++ .../search/browser/searchActionsFind.ts | 384 +++++++ .../search/browser/searchActionsNav.ts | 533 ++++++++++ .../browser/searchActionsRemoveReplace.ts | 408 ++++++++ .../search/browser/searchActionsSymbol.ts | 48 + .../search/browser/searchActionsTopBar.ts | 348 +++++++ .../search/browser/searchResultsView.ts | 113 ++- .../contrib/search/browser/searchView.ts | 7 +- .../contrib/search/browser/searchWidget.ts | 2 +- .../contrib/search/common/constants.ts | 5 + .../search/test/browser/searchActions.test.ts | 2 +- .../browser/searchEditor.contribution.ts | 2 +- .../browser/searchEditorActions.ts | 2 +- .../terminal/browser/terminalActions.ts | 4 +- 18 files changed, 2135 insertions(+), 1778 deletions(-) delete mode 100644 src/vs/workbench/contrib/search/browser/searchActions.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsBase.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsCopy.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsFind.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsNav.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts create mode 100644 src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 53460c60090..a39a46fd6c5 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -103,6 +103,7 @@ export class MenuId { static readonly SCMSourceControl = new MenuId('SCMSourceControl'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); + static readonly SearchActionMenu = new MenuId('SearchActionContext'); static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu'); static readonly StatusBarRemoteIndicatorMenu = new MenuId('StatusBarRemoteIndicatorMenu'); static readonly StickyScrollContext = new MenuId('StickyScrollContext'); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index f0f6746a40f..b6f74172133 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -3,58 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { dirname } from 'vs/base/common/resources'; -import { assertIsDefined, assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/browser/findModel'; import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess'; import * as nls from 'vs/nls'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ICommandAction } from 'vs/platform/action/common/action'; -import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IListService, WorkbenchListFocusContextKey, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { Extensions as QuickAccessExtensions, IQuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; -import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; -import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { cancelSearch, clearHistoryCommand, clearSearchResults, CloseReplaceAction, collapseDeepestExpandedLevel, copyAllCommand, copyMatchCommand, copyPathCommand, expandAll, FindInFilesCommand, findOrReplaceInFiles, FocusNextInputAction, focusNextSearchResult, FocusPreviousInputAction, focusPreviousSearchResult, focusSearchListCommand, getMultiSelectedSearchResources, getSearchView, openSearchView, refreshSearch, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, toggleCaseSensitiveCommand, togglePreserveCaseCommand, toggleRegexCommand, toggleWholeWordCommand } from 'vs/workbench/contrib/search/browser/searchActions'; -import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchStopIcon, searchShowAsTree, searchViewIcon, searchShowAsList } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { searchViewIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; -import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { resolveResourcesForSearchIncludes } from 'vs/workbench/services/search/common/queryBuilder'; -import { getWorkspaceSymbols, IWorkspaceSymbol, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, ISearchWorkbenchService, Match, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; -import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; -import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ISearchWorkbenchService, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { ISearchConfiguration, SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, ViewMode, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, ViewMode, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; + +import 'vs/workbench/contrib/search/browser/searchActionsCopy'; +import 'vs/workbench/contrib/search/browser/searchActionsFind'; +import 'vs/workbench/contrib/search/browser/searchActionsNav'; +import 'vs/workbench/contrib/search/browser/searchActionsRemoveReplace'; +import 'vs/workbench/contrib/search/browser/searchActionsSymbol'; +import 'vs/workbench/contrib/search/browser/searchActionsTopBar'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, InstantiationType.Delayed); registerSingleton(ISearchHistoryService, SearchHistoryService, InstantiationType.Delayed); @@ -62,710 +48,6 @@ registerSingleton(ISearchHistoryService, SearchHistoryService, InstantiationType replaceContributions(); searchWidgetContributions(); -const category = { value: nls.localize('search', "Search"), original: 'Search' }; - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.ToggleQueryDetailsActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.or(Constants.SearchViewFocusedKey, SearchEditorConstants.InSearchEditor), - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyJ, - handler: accessor => { - const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); - if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { - (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(); - } else if (contextService.getValue(Constants.SearchViewFocusedKey.serialize())) { - const searchView = getSearchView(accessor.get(IViewsService)); - assertIsDefined(searchView).toggleQueryDetails(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.FocusSearchFromResults, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FirstMatchFocusKey), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - searchView?.focusPreviousInputBox(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.OpenMatch, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), - primary: KeyCode.Enter, - mac: { - primary: KeyCode.Enter, - secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow] - }, - handler: (accessor) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - const viewer = searchView.getControl(); - const focus = tree.getFocus()[0]; - - if (focus instanceof FolderMatch) { - viewer.toggleCollapsed(focus); - } else { - searchView.open(tree.getFocus()[0], false, false, true); - } - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.OpenMatchToSide, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), - primary: KeyMod.CtrlCmd | KeyCode.Enter, - mac: { - primary: KeyMod.WinCtrl | KeyCode.Enter - }, - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - searchView.open(tree.getFocus()[0], false, true, true); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.RemoveActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), - primary: KeyCode.Delete, - mac: { - primary: KeyMod.CtrlCmd | KeyCode.Backspace, - }, - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]!).run(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.ReplaceActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.MatchFocusKey), - primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0] as Match, searchView).run(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.ReplaceAllInFileActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FileFocusKey), - primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0] as FileMatch).run(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.ReplaceAllInFolderActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FolderFocusKey), - primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0] as FolderMatch).run(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.CloseReplaceWidgetActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey), - primary: KeyCode.Escape, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(CloseReplaceAction, Constants.CloseReplaceWidgetActionId, '').run(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: FocusNextInputAction.ID, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.or( - ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), - ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey)), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(FocusNextInputAction, FocusNextInputAction.ID, '').run(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: FocusPreviousInputAction.ID, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.or( - ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), - ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey, Constants.SearchInputBoxFocusedKey.toNegated())), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(FocusPreviousInputAction, FocusPreviousInputAction.ID, '').run(); - } -}); - -const restrictSearchToFolderFromSearch: ICommandHandler = async (accessor, folderMatch?: FolderMatchWithResource) => { - return searchWithFolderCommand(accessor, false, true, undefined, folderMatch); -}; -const excludeFolderFromSearch: ICommandHandler = async (accessor, folderMatch?: FolderMatchWithResource) => { - return searchWithFolderCommand(accessor, false, false, undefined, folderMatch); -}; -const RESTRICT_SEARCH_TO_FOLDER_ID = 'search.restrictSearchToFolder'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: RESTRICT_SEARCH_TO_FOLDER_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ResourceFolderFocusKey), - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyF, - handler: restrictSearchToFolderFromSearch -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.ExcludeFolderFromSearchId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ResourceFolderFocusKey), - handler: excludeFolderFromSearch -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.ReplaceActionId, - title: ReplaceAction.LABEL - }, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.MatchFocusKey), - group: 'search', - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.ReplaceAllInFolderActionId, - title: ReplaceAllInFolderAction.LABEL - }, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FolderFocusKey), - group: 'search', - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.ReplaceAllInFileActionId, - title: ReplaceAllAction.LABEL - }, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FileFocusKey), - group: 'search', - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.RemoveActionId, - title: RemoveAction.LABEL - }, - when: Constants.FileMatchOrMatchFocusKey, - group: 'search', - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - group: 'search', - order: 3, - command: { - id: RESTRICT_SEARCH_TO_FOLDER_ID, - title: nls.localize('restrictResultsToFolder', "Restrict Search to Folder") - }, - when: ContextKeyExpr.and(Constants.ResourceFolderFocusKey) -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - group: 'search', - order: 4, - command: { - id: Constants.ExcludeFolderFromSearchId, - title: nls.localize('excludeFolderFromSearch', "Exclude Folder from Search") - }, - when: ContextKeyExpr.and(Constants.ResourceFolderFocusKey) -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.CopyMatchCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: Constants.FileMatchOrMatchFocusKey, - primary: KeyMod.CtrlCmd | KeyCode.KeyC, - handler: copyMatchCommand -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.CopyMatchCommandId, - title: nls.localize('copyMatchLabel', "Copy") - }, - when: Constants.FileMatchOrMatchFocusKey, - group: 'search_2', - order: 1 -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.CopyPathCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: Constants.FileMatchOrFolderMatchWithResourceFocusKey, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC, - win: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC - }, - handler: copyPathCommand -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.CopyPathCommandId, - title: nls.localize('copyPathLabel', "Copy Path") - }, - when: Constants.FileMatchOrFolderMatchWithResourceFocusKey, - group: 'search_2', - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.CopyAllCommandId, - title: nls.localize('copyAllLabel', "Copy All") - }, - when: Constants.HasSearchResults, - group: 'search_2', - order: 3 -}); - -CommandsRegistry.registerCommand({ - id: Constants.CopyAllCommandId, - handler: copyAllCommand -}); - -CommandsRegistry.registerCommand({ - id: Constants.ClearSearchHistoryCommandId, - handler: clearHistoryCommand -}); - -CommandsRegistry.registerCommand({ - id: Constants.RevealInSideBarForSearchResults, - handler: (accessor, args: any) => { - const paneCompositeService = accessor.get(IPaneCompositePartService); - const explorerService = accessor.get(IExplorerService); - const contextService = accessor.get(IWorkspaceContextService); - - const searchView = getSearchView(accessor.get(IViewsService)); - if (!searchView) { - return; - } - - let fileMatch: FileMatch; - if (!(args instanceof FileMatch)) { - args = searchView.getControl().getFocus()[0]; - } - if (args instanceof FileMatch) { - fileMatch = args; - } else { - return; - } - - paneCompositeService.openPaneComposite(VIEWLET_ID_FILES, ViewContainerLocation.Sidebar, false).then((viewlet) => { - if (!viewlet) { - return; - } - - const explorerViewContainer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer; - const uri = fileMatch.resource; - if (uri && contextService.isInsideWorkspace(uri)) { - const explorerView = explorerViewContainer.getExplorerView(); - explorerView.setExpanded(true); - explorerService.select(uri, true).then(() => explorerView.focus(), onUnexpectedError); - } - }); - } -}); - -registerAction2(class CancelSearchAction extends Action2 { - constructor() { - super({ - id: Constants.CancelSearchActionId, - title: { - value: nls.localize('CancelSearchAction.label', "Cancel Search"), - original: 'Cancel Search' - }, - icon: searchStopIcon, - category, - f1: true, - precondition: SearchStateKey.isEqualTo(SearchUIState.Idle).negate(), - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey), - primary: KeyCode.Escape, - }, - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 0, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), SearchStateKey.isEqualTo(SearchUIState.SlowSearch)), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - return cancelSearch(accessor); - } -}); - -registerAction2(class RefreshAction extends Action2 { - constructor() { - super({ - id: Constants.RefreshSearchResultsActionId, - title: { - value: nls.localize('RefreshAction.label', "Refresh"), - original: 'Refresh' - }, - icon: searchRefreshIcon, - precondition: Constants.ViewHasSearchPatternKey, - category, - f1: true, - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 0, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), SearchStateKey.isEqualTo(SearchUIState.SlowSearch).negate()), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - return refreshSearch(accessor); - } -}); - -registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { - constructor() { - super({ - id: Constants.CollapseSearchResultsActionId, - title: { - value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), - original: 'Collapse All' - }, - category, - icon: searchCollapseAllIcon, - f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey), - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ContextKeyExpr.or(Constants.HasSearchResults.negate(), Constants.ViewHasSomeCollapsibleKey)), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - return collapseDeepestExpandedLevel(accessor); - } -}); - -registerAction2(class ExpandAllAction extends Action2 { - constructor() { - super({ - id: Constants.ExpandSearchResultsActionId, - title: { - value: nls.localize('ExpandAllAction.label', "Expand All"), - original: 'Expand All' - }, - category, - icon: searchExpandAllIcon, - f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - return expandAll(accessor); - } -}); - -registerAction2(class ClearSearchResultsAction extends Action2 { - constructor() { - super({ - id: Constants.ClearSearchResultsActionId, - title: { - value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), - original: 'Clear Search Results' - }, - category, - icon: searchClearIcon, - f1: true, - precondition: ContextKeyExpr.or(Constants.HasSearchResults, Constants.ViewHasSearchPatternKey, Constants.ViewHasReplacePatternKey, Constants.ViewHasFilePatternKey), - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 1, - when: ContextKeyExpr.equals('view', VIEW_ID), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - return clearSearchResults(accessor); - } -}); - -registerAction2(class ViewAsTreeAction extends Action2 { - constructor() { - super({ - id: Constants.ViewAsTreeActionId, - title: { - value: nls.localize('ViewAsTreeAction.label', "View as Tree"), - original: 'View as Tree' - }, - category, - icon: searchShowAsList, - f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.InTreeViewKey.toNegated()), - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey.toNegated()), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - searchView.setTreeView(true); - } - } -}); - - -registerAction2(class ViewAsListAction extends Action2 { - constructor() { - super({ - id: Constants.ViewAsListActionId, - title: { - value: nls.localize('ViewAsListAction.label', "View as List"), - original: 'View as List' - }, - category, - icon: searchShowAsTree, - f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.InTreeViewKey), - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey), - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - searchView.setTreeView(false); - } - } -}); - -const RevealInSideBarForSearchResultsCommand: ICommandAction = { - id: Constants.RevealInSideBarForSearchResults, - title: nls.localize('revealInSideBar', "Reveal in Explorer View") -}; - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: RevealInSideBarForSearchResultsCommand, - when: ContextKeyExpr.and(Constants.FileFocusKey, Constants.HasSearchResults), - group: 'search_3', - order: 1 -}); - -const ClearSearchHistoryCommand: ICommandAction = { - id: Constants.ClearSearchHistoryCommandId, - title: { value: nls.localize('clearSearchHistoryLabel', "Clear Search History"), original: 'Clear Search History' }, - category -}; -MenuRegistry.addCommand(ClearSearchHistoryCommand); - -CommandsRegistry.registerCommand({ - id: Constants.FocusSearchListCommandID, - handler: focusSearchListCommand -}); - -const FocusSearchListCommand: ICommandAction = { - id: Constants.FocusSearchListCommandID, - title: { value: nls.localize('focusSearchListCommandLabel', "Focus List"), original: 'Focus List' }, - category -}; -MenuRegistry.addCommand(FocusSearchListCommand); - -const searchInFolderFromExplorer: ICommandHandler = async (accessor, resource?: URI) => { - return searchWithFolderCommand(accessor, true, true, resource); -}; - -const searchWithFolderCommand: ICommandHandler = async (accessor, isFromExplorer: boolean, isIncludes: boolean, resource?: URI, folderMatch?: FolderMatchWithResource) => { - const listService = accessor.get(IListService); - const fileService = accessor.get(IFileService); - const viewsService = accessor.get(IViewsService); - const contextService = accessor.get(IWorkspaceContextService); - const commandService = accessor.get(ICommandService); - const searchConfig = accessor.get(IConfigurationService).getValue().search; - const mode = searchConfig.mode; - - let resources: URI[]; - - if (isFromExplorer) { - resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IExplorerService)); - } else { - const searchView = getSearchView(accessor.get(IViewsService)); - if (!searchView) { - return; - } - resources = getMultiSelectedSearchResources(searchView.getControl(), folderMatch, searchConfig); - } - - const resolvedResources = fileService.resolveAll(resources.map(resource => ({ resource }))).then(results => { - const folders: URI[] = []; - results.forEach(result => { - if (result.success && result.stat) { - folders.push(result.stat.isDirectory ? result.stat.resource : dirname(result.stat.resource)); - } - }); - return resolveResourcesForSearchIncludes(folders, contextService); - }); - - if (mode === 'view') { - const searchView = await openSearchView(viewsService, true); - if (resources && resources.length && searchView) { - if (isIncludes) { - searchView.searchInFolders(await resolvedResources); - } else { - searchView.searchOutsideOfFolders(await resolvedResources); - } - } - return undefined; - } else { - if (isIncludes) { - return commandService.executeCommand(SearchEditorConstants.OpenEditorCommandId, { - filesToInclude: (await resolvedResources).join(', '), - showIncludesExcludes: true, - location: mode === 'newEditor' ? 'new' : 'reuse', - }); - } - else { - return commandService.executeCommand(SearchEditorConstants.OpenEditorCommandId, { - filesToExclude: (await resolvedResources).join(', '), - showIncludesExcludes: true, - location: mode === 'newEditor' ? 'new' : 'reuse', - }); - } - } -}; - -const FIND_IN_FOLDER_EXPLORER_ID = 'filesExplorer.findInFolder'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: FIND_IN_FOLDER_EXPLORER_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext), - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyF, - handler: searchInFolderFromExplorer -}); - -const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace'; -CommandsRegistry.registerCommand({ - id: FIND_IN_WORKSPACE_ID, - handler: async (accessor) => { - const searchConfig = accessor.get(IConfigurationService).getValue().search; - const mode = searchConfig.mode; - - if (mode === 'view') { - const searchView = await openSearchView(accessor.get(IViewsService), true); - searchView?.searchInFolders(); - } - else { - return accessor.get(ICommandService).executeCommand(SearchEditorConstants.OpenEditorCommandId, { - location: mode === 'newEditor' ? 'new' : 'reuse', - filesToInclude: '', - }); - } - } -}); - -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: '4_search', - order: 10, - command: { - id: FIND_IN_FOLDER_EXPLORER_ID, - title: nls.localize('findInFolder', "Find in Folder...") - }, - when: ContextKeyExpr.and(ExplorerFolderContext) -}); - -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: '4_search', - order: 10, - command: { - id: FIND_IN_WORKSPACE_ID, - title: nls.localize('findInWorkspace', "Find in Workspace...") - }, - when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext.toNegated()) -}); - -class ShowAllSymbolsAction extends Action2 { - - static readonly ID = 'workbench.action.showAllSymbols'; - static readonly LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace..."); - static readonly ALL_SYMBOLS_PREFIX = '#'; - - constructor( - ) { - super({ - id: Constants.ShowAllSymbolsActionId, - title: { - value: nls.localize('showTriggerActions', "Go to Symbol in Workspace..."), - original: 'Go to Symbol in Workspace...' - }, - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyT - } - }); - } - - override async run(accessor: ServicesAccessor): Promise { - accessor.get(IQuickInputService).quickAccess.show(ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX); - } -} - -registerAction2(ShowAllSymbolsAction); - const SEARCH_MODE_CONFIG = 'search.mode'; const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ @@ -799,7 +81,6 @@ const viewDescriptor: IViewDescriptor = { // Register search default location to sidebar Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer); - // Migrate search location setting to new model class RegisterSearchViewContribution implements IWorkbenchContribution { constructor( @@ -816,200 +97,6 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterSearchViewContribution, LifecyclePhase.Starting); -// Find in Files by default is the same as View: Show Search, but can be configured to open a search editor instead with the `search.mode` binding -KeybindingsRegistry.registerCommandAndKeybindingRule({ - description: { - description: nls.localize('findInFiles.description', "Open a workspace search"), - args: [ - { - name: nls.localize('findInFiles.args', "A set of options for the search"), - schema: { - type: 'object', - properties: { - query: { 'type': 'string' }, - replace: { 'type': 'string' }, - preserveCase: { 'type': 'boolean' }, - triggerSearch: { 'type': 'boolean' }, - filesToInclude: { 'type': 'string' }, - filesToExclude: { 'type': 'string' }, - isRegex: { 'type': 'boolean' }, - isCaseSensitive: { 'type': 'boolean' }, - matchWholeWord: { 'type': 'boolean' }, - useExcludeSettingsAndIgnoreFiles: { 'type': 'boolean' }, - onlyOpenEditors: { 'type': 'boolean' }, - } - } - }, - ] - }, - id: Constants.FindInFilesActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: null, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyF, - handler: FindInFilesCommand -}); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: Constants.FindInFilesActionId, title: { value: nls.localize('findInFiles', "Find in Files"), original: 'Find in Files' }, category } }); -MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { - group: '4_find_global', - command: { - id: Constants.FindInFilesActionId, - title: nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files") - }, - order: 1 -}); - -registerAction2(class FocusNextSearchResultAction extends Action2 { - constructor() { - super({ - id: Constants.FocusNextSearchResultActionId, - title: { - value: nls.localize('FocusNextSearchResult.label', 'Focus Next Search Result'), - original: 'Focus Next Search Result' - }, - keybinding: [{ - primary: KeyCode.F4, - weight: KeybindingWeight.WorkbenchContrib, - }], - category: category.value, - - precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), - }); - } - - override async run(accessor: ServicesAccessor): Promise { - return focusNextSearchResult(accessor); - } -}); - -registerAction2(class FocusPreviousSearchResultAction extends Action2 { - constructor() { - super({ - id: Constants.FocusPreviousSearchResultActionId, - title: { - value: nls.localize('FocusPreviousSearchResult.label', 'Search: Focus Previous Search Result'), - original: 'Search: Focus Previous Search Result' - }, - keybinding: [{ - primary: KeyMod.Shift | KeyCode.F4, - weight: KeybindingWeight.WorkbenchContrib, - }], - category: category.value, - - precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), - }); - } - - override async run(accessor: ServicesAccessor): Promise { - return focusPreviousSearchResult(accessor); - } -}); - -registerAction2(class ReplaceInFilesAction extends Action2 { - constructor() { - super({ - id: Constants.ReplaceInFilesActionId, - title: { - value: nls.localize('replaceInFiles', 'Search: Replace in Files'), - original: 'Search: Replace in Files' - }, - keybinding: [{ - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyH, - weight: KeybindingWeight.WorkbenchContrib, - }], - category: category.value, - }); - } - - override async run(accessor: ServicesAccessor): Promise { - return findOrReplaceInFiles(accessor, true); - } -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { - group: '4_find_global', - command: { - id: Constants.ReplaceInFilesActionId, - title: nls.localize({ key: 'miReplaceInFiles', comment: ['&& denotes a mnemonic'] }, "Replace &&in Files") - }, - order: 2 -}); - -if (platform.isMacintosh) { - // Register this with a more restrictive `when` on mac to avoid conflict with "copy path" - KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: Constants.ToggleCaseSensitiveCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()), - handler: toggleCaseSensitiveCommand - }, ToggleCaseSensitiveKeybinding)); -} else { - KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: Constants.ToggleCaseSensitiveCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, - handler: toggleCaseSensitiveCommand - }, ToggleCaseSensitiveKeybinding)); -} - -KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: Constants.ToggleWholeWordCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, - handler: toggleWholeWordCommand -}, ToggleWholeWordKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: Constants.ToggleRegexCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, - handler: toggleRegexCommand -}, ToggleRegexKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ - id: Constants.TogglePreserveCaseId, - weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, - handler: togglePreserveCaseCommand -}, TogglePreserveCaseKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.AddCursorsAtSearchResults, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL, - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); - searchView.openEditorWithMultiCursor(tree.getFocus()[0]); - } - } -}); -// --- Toggle Search On Type - -registerAction2(class ToggleSearchOnTypeAction extends Action2 { - private static readonly searchOnTypeKey = 'search.searchOnType'; - - constructor( - ) { - super({ - id: Constants.ToggleSearchOnTypeActionId, - title: { - value: nls.localize('toggleTabs', 'Toggle Search on Type'), - original: 'Toggle Search on Type' - }, - category: category.value, - }); - - } - - override async run(accessor: ServicesAccessor): Promise { - const configurationService = accessor.get(IConfigurationService); - const searchOnType = configurationService.getValue(ToggleSearchOnTypeAction.searchOnTypeKey); - return configurationService.updateValue(ToggleSearchOnTypeAction.searchOnTypeKey, !searchOnType); - } -}); - // Register Quick Access Handler const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); @@ -1026,7 +113,7 @@ quickAccessRegistry.registerQuickAccessProvider({ prefix: SymbolsQuickAccessProvider.PREFIX, placeholder: nls.localize('symbolsQuickAccessPlaceholder', "Type the name of a symbol to open."), contextKey: 'inWorkspaceSymbolsPicker', - helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), commandId: ShowAllSymbolsAction.ID }] + helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), commandId: Constants.ShowAllSymbolsActionId }] }); // Configuration @@ -1272,14 +359,3 @@ CommandsRegistry.registerCommand('_executeWorkspaceSymbolProvider', async functi const result = await getWorkspaceSymbols(query); return result.map(item => item.symbol); }); - -// Go to menu - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '3_global_nav', - command: { - id: Constants.ShowAllSymbolsActionId, - title: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...") - }, - order: 2 -}); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts deleted file mode 100644 index 87ccd82a90a..00000000000 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ /dev/null @@ -1,778 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as DOM from 'vs/base/browser/dom'; -import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; -import { Action, IAction } from 'vs/base/common/actions'; -import { createKeybinding, ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { isWindows, OS } from 'vs/base/common/platform'; -import * as nls from 'vs/nls'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { getSelectionKeyboardEvent, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IViewsService } from 'vs/workbench/common/views'; -import { searchRemoveIcon, searchReplaceAllIcon, searchReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { arrayContainsElementOrParent, FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWithResource, FolderMatchWorkspaceRoot, Match, RenderableMatch, searchComparer, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; -import { OpenEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; -import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; -import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ISearchConfiguration, ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { URI } from 'vs/base/common/uri'; - -export function isSearchViewFocused(viewsService: IViewsService): boolean { - const searchView = getSearchView(viewsService); - const activeElement = document.activeElement; - return !!(searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer())); -} - -export function appendKeyBindingLabel(label: string, inputKeyBinding: number | ResolvedKeybinding | undefined, keyBindingService2: IKeybindingService): string { - if (typeof inputKeyBinding === 'number') { - const keybinding = createKeybinding(inputKeyBinding, OS); - if (keybinding) { - const resolvedKeybindings = keyBindingService2.resolveKeybinding(keybinding); - return doAppendKeyBindingLabel(label, resolvedKeybindings.length > 0 ? resolvedKeybindings[0] : undefined); - } - return doAppendKeyBindingLabel(label, undefined); - } else { - return doAppendKeyBindingLabel(label, inputKeyBinding); - } -} - -export function openSearchView(viewsService: IViewsService, focus?: boolean): Promise { - return viewsService.openView(VIEW_ID, focus).then(view => (view as SearchView ?? undefined)); -} - -export function getSearchView(viewsService: IViewsService): SearchView | undefined { - return viewsService.getActiveViewWithId(VIEW_ID) as SearchView ?? undefined; -} - -function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding | undefined): string { - return keyBinding ? label + ' (' + keyBinding.getLabel() + ')' : label; -} - -export const toggleCaseSensitiveCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewsService)); - searchView?.toggleCaseSensitive(); -}; - -export const toggleWholeWordCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewsService)); - searchView?.toggleWholeWords(); -}; - -export const toggleRegexCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewsService)); - searchView?.toggleRegex(); -}; - -export const togglePreserveCaseCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewsService)); - searchView?.togglePreserveCase(); -}; - -export class FocusNextInputAction extends Action { - - static readonly ID = 'search.focus.nextInputBox'; - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService, - @IEditorService private readonly editorService: IEditorService, - ) { - super(id, label); - } - - override async run(): Promise { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - (this.editorService.activeEditorPane as SearchEditor).focusNextInput(); - } - - const searchView = getSearchView(this.viewsService); - searchView?.focusNextInputBox(); - } -} - -export class FocusPreviousInputAction extends Action { - - static readonly ID = 'search.focus.previousInputBox'; - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService, - @IEditorService private readonly editorService: IEditorService, - ) { - super(id, label); - } - - override async run(): Promise { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - (this.editorService.activeEditorPane as SearchEditor).focusPrevInput(); - } - - const searchView = getSearchView(this.viewsService); - searchView?.focusPreviousInputBox(); - } -} - -export interface IFindInFilesArgs { - query?: string; - replace?: string; - preserveCase?: boolean; - triggerSearch?: boolean; - filesToInclude?: string; - filesToExclude?: string; - isRegex?: boolean; - isCaseSensitive?: boolean; - matchWholeWord?: boolean; - useExcludeSettingsAndIgnoreFiles?: boolean; - onlyOpenEditors?: boolean; -} - -export const FindInFilesCommand: ICommandHandler = (accessor, args: IFindInFilesArgs = {}) => { - const searchConfig = accessor.get(IConfigurationService).getValue().search; - const mode = searchConfig.mode; - if (mode === 'view') { - const viewsService = accessor.get(IViewsService); - openSearchView(viewsService, false).then(openedView => { - if (openedView) { - const searchAndReplaceWidget = openedView.searchAndReplaceWidget; - searchAndReplaceWidget.toggleReplace(typeof args.replace === 'string'); - let updatedText = false; - if (typeof args.query === 'string') { - openedView.setSearchParameters(args); - } else { - updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: typeof args.replace !== 'string' }); - } - openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); - } - }); - } else { - const convertArgs = (args: IFindInFilesArgs): OpenSearchEditorArgs => ({ - location: mode === 'newEditor' ? 'new' : 'reuse', - query: args.query, - filesToInclude: args.filesToInclude, - filesToExclude: args.filesToExclude, - matchWholeWord: args.matchWholeWord, - isCaseSensitive: args.isCaseSensitive, - isRegexp: args.isRegex, - useExcludeSettingsAndIgnoreFiles: args.useExcludeSettingsAndIgnoreFiles, - onlyOpenEditors: args.onlyOpenEditors, - showIncludesExcludes: !!(args.filesToExclude || args.filesToExclude || !args.useExcludeSettingsAndIgnoreFiles), - }); - accessor.get(ICommandService).executeCommand(OpenEditorCommandId, convertArgs(args)); - } -}; - -export class CloseReplaceAction extends Action { - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label); - } - - override run(): Promise { - const searchView = getSearchView(this.viewsService); - if (searchView) { - searchView.searchAndReplaceWidget.toggleReplace(false); - searchView.searchAndReplaceWidget.focus(); - } - return Promise.resolve(null); - } -} - -export function expandAll(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); - const searchView = getSearchView(viewsService); - if (searchView) { - const viewer = searchView.getControl(); - viewer.expandAll(); - } -} - -export function clearSearchResults(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); - const searchView = getSearchView(viewsService); - searchView?.clearSearchResults(); -} - -export function cancelSearch(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); - const searchView = getSearchView(viewsService); - searchView?.cancelSearch(); -} - -export function refreshSearch(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); - const searchView = getSearchView(viewsService); - searchView?.triggerQueryChange({ preserveFocus: false }); -} - -export function collapseDeepestExpandedLevel(accessor: ServicesAccessor) { - - const viewsService = accessor.get(IViewsService); - const searchView = getSearchView(viewsService); - if (searchView) { - const viewer = searchView.getControl(); - - /** - * one level to collapse so collapse everything. If FolderMatch, check if there are visible grandchildren, - * i.e. if Matches are returned by the navigator, and if so, collapse to them, otherwise collapse all levels. - */ - const navigator = viewer.navigate(); - let node = navigator.first(); - let canCollapseFileMatchLevel = false; - let canCollapseFirstLevel = false; - - if (node instanceof FolderMatchWorkspaceRoot) { - while (node = navigator.next()) { - if (node instanceof Match) { - canCollapseFileMatchLevel = true; - break; - } - if (searchView.isTreeLayoutViewVisible && !canCollapseFirstLevel) { - let nodeToTest = node; - - if (node instanceof FolderMatch) { - nodeToTest = node.compressionStartParent ?? node; - } - - const immediateParent = nodeToTest.parent(); - - if (!(immediateParent instanceof FolderMatchWorkspaceRoot || immediateParent instanceof FolderMatchNoRoot || immediateParent instanceof SearchResult)) { - canCollapseFirstLevel = true; - } - } - } - } - - if (canCollapseFileMatchLevel) { - node = navigator.first(); - do { - if (node instanceof FileMatch) { - viewer.collapse(node); - } - } while (node = navigator.next()); - } else if (canCollapseFirstLevel) { - node = navigator.first(); - if (node) { - do { - - let nodeToTest = node; - - if (node instanceof FolderMatch) { - nodeToTest = node.compressionStartParent ?? node; - } - const immediateParent = nodeToTest.parent(); - - if (immediateParent instanceof FolderMatchWorkspaceRoot || immediateParent instanceof FolderMatchNoRoot) { - if (viewer.hasElement(node)) { - viewer.collapse(node, true); - } else { - viewer.collapseAll(); - } - } - } while (node = navigator.next()); - } - } else { - viewer.collapseAll(); - } - - const firstFocusParent = viewer.getFocus()[0]?.parent(); - - if (firstFocusParent && (firstFocusParent instanceof FolderMatch || firstFocusParent instanceof FileMatch) && - viewer.hasElement(firstFocusParent) && viewer.isCollapsed(firstFocusParent)) { - viewer.domFocus(); - viewer.focusFirst(); - viewer.setSelection(viewer.getFocus()); - } - } -} - -export async function focusNextSearchResult(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const input = editorService.activeEditor; - if (input instanceof SearchEditorInput) { - // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - return (editorService.activeEditorPane as SearchEditor).focusNextResult(); - } - - return openSearchView(accessor.get(IViewsService)).then(searchView => { - searchView?.selectNextMatch(); - }); -} - -export async function focusPreviousSearchResult(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const input = editorService.activeEditor; - if (input instanceof SearchEditorInput) { - // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - return (editorService.activeEditorPane as SearchEditor).focusPreviousResult(); - } - - return openSearchView(accessor.get(IViewsService)).then(searchView => { - searchView?.selectPreviousMatch(); - }); -} - -export async function findOrReplaceInFiles(accessor: ServicesAccessor, expandSearchReplaceWidget: boolean): Promise { - return openSearchView(accessor.get(IViewsService), false).then(openedView => { - if (openedView) { - const searchAndReplaceWidget = openedView.searchAndReplaceWidget; - searchAndReplaceWidget.toggleReplace(expandSearchReplaceWidget); - - const updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: !expandSearchReplaceWidget }); - openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); - } - }); -} - - -class ReplaceActionRunner { - constructor( - private viewer: WorkbenchCompressibleObjectTree, - private viewlet: SearchView | undefined, - // Services - @IReplaceService private readonly replaceService: IReplaceService, - @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IViewsService private readonly viewsService: IViewsService - ) { } - - async performReplace(element: RenderableMatch): Promise { - // since multiple elements can be selected, we need to check the type of the FolderMatch/FileMatch/Match before we perform the replace. - const opInfo = getElementsToOperateOnInfo(this.viewer, element, this.configurationService.getValue('search')); - const elementsToReplace = opInfo.elements; - let focusElement = this.viewer.getFocus()[0]; - - if (!focusElement || (focusElement && !arrayContainsElementOrParent(focusElement, elementsToReplace)) || (focusElement instanceof SearchResult)) { - focusElement = element; - } - - if (elementsToReplace.length === 0) { - return; - } - let nextFocusElement; - if (focusElement) { - nextFocusElement = getElementToFocusAfterRemoved(this.viewer, focusElement, elementsToReplace); - } - - const searchResult = getSearchView(this.viewsService)?.searchResult; - - if (searchResult) { - searchResult.batchReplace(elementsToReplace); - } - - if (focusElement) { - if (!nextFocusElement) { - nextFocusElement = getLastNodeFromSameType(this.viewer, focusElement); - } - - if (nextFocusElement) { - this.viewer.reveal(nextFocusElement); - this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); - this.viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); - - if (nextFocusElement instanceof Match) { - const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; - if (!useReplacePreview || this.hasToOpenFile(nextFocusElement)) { - this.viewlet?.open(nextFocusElement, true); - } else { - this.replaceService.openReplacePreview(nextFocusElement, true); - } - } else if (nextFocusElement instanceof FileMatch) { - this.viewlet?.open(nextFocusElement, true); - } - } - - } - - this.viewer.domFocus(); - } - - private hasToOpenFile(currBottomElem: RenderableMatch): boolean { - if (!(currBottomElem instanceof Match)) { - return false; - } - const activeEditor = this.editorService.activeEditor; - const file = activeEditor?.resource; - if (file) { - return this.uriIdentityService.extUri.isEqual(file, currBottomElem.parent().resource); - } - return false; - } -} - -export class RemoveAction implements IAction { - - static readonly LABEL = nls.localize('RemoveAction.label', "Dismiss"); - - readonly id = Constants.RemoveActionId; - public class = ThemeIcon.asClassName(searchRemoveIcon); - public label: string; - public tooltip = ''; - public enabled = true; - - constructor( - private readonly viewer: WorkbenchCompressibleObjectTree, - private readonly element: RenderableMatch, - @IKeybindingService keyBindingService: IKeybindingService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IViewsService private readonly viewsService: IViewsService, - ) { - this.label = appendKeyBindingLabel(RemoveAction.LABEL, keyBindingService.lookupKeybinding(Constants.RemoveActionId), keyBindingService); - } - - run(): void { - const opInfo = getElementsToOperateOnInfo(this.viewer, this.element, this.configurationService.getValue('search')); - const elementsToRemove = opInfo.elements; - let focusElement = this.viewer.getFocus()[0]; - - if (elementsToRemove.length === 0) { - return; - } - - if (!focusElement || (focusElement instanceof SearchResult)) { - focusElement = this.element; - } - - let nextFocusElement; - if (opInfo.mustReselect && focusElement) { - nextFocusElement = getElementToFocusAfterRemoved(this.viewer, focusElement, elementsToRemove); - } - - const searchResult = getSearchView(this.viewsService)?.searchResult; - - if (searchResult) { - searchResult.batchRemove(elementsToRemove); - } - - if (opInfo.mustReselect && focusElement) { - if (!nextFocusElement) { - nextFocusElement = getLastNodeFromSameType(this.viewer, focusElement); - } - - if (nextFocusElement && !arrayContainsElementOrParent(nextFocusElement, elementsToRemove)) { - this.viewer.reveal(nextFocusElement); - this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); - this.viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); - } - } - - this.viewer.domFocus(); - return; - } -} - -export class ReplaceAction extends Action { - - static readonly LABEL = nls.localize('match.replace.label', "Replace"); - - static runQ = Promise.resolve(); - private replaceRunner: ReplaceActionRunner; - - constructor(viewer: WorkbenchCompressibleObjectTree, private element: Match, viewlet: SearchView, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IKeybindingService keyBindingService: IKeybindingService, - ) { - super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceIcon)); - this.replaceRunner = this.instantiationService.createInstance(ReplaceActionRunner, viewer, viewlet); - } - - override async run(): Promise { - return this.replaceRunner.performReplace(this.element); - } -} - -export class ReplaceAllAction extends Action { - - static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All"); - private replaceRunner: ReplaceActionRunner; - constructor( - viewlet: SearchView, - private fileMatch: FileMatch, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IKeybindingService keyBindingService: IKeybindingService, - ) { - super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); - this.replaceRunner = this.instantiationService.createInstance(ReplaceActionRunner, viewlet.getControl(), viewlet); - } - - override async run(): Promise { - return this.replaceRunner.performReplace(this.fileMatch); - } -} - -export class ReplaceAllInFolderAction extends Action { - - static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All"); - private replaceRunner: ReplaceActionRunner; - - constructor(viewer: WorkbenchCompressibleObjectTree, private folderMatch: FolderMatch, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IKeybindingService keyBindingService: IKeybindingService - ) { - super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); - this.replaceRunner = this.instantiationService.createInstance(ReplaceActionRunner, viewer, undefined); - } - - override run(): Promise { - return this.replaceRunner.performReplace(this.folderMatch); - } -} - -export const copyPathCommand: ICommandHandler = async (accessor, fileMatch: FileMatch | FolderMatchWithResource | undefined) => { - if (!fileMatch) { - const selection = getSelectedRow(accessor); - if (!(selection instanceof FileMatch || selection instanceof FolderMatchWithResource)) { - return; - } - - fileMatch = selection; - } - - const clipboardService = accessor.get(IClipboardService); - const labelService = accessor.get(ILabelService); - - const text = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); - await clipboardService.writeText(text); -}; - -function matchToString(match: Match, indent = 0): string { - const getFirstLinePrefix = () => `${match.range().startLineNumber},${match.range().startColumn}`; - const getOtherLinePrefix = (i: number) => match.range().startLineNumber + i + ''; - - const fullMatchLines = match.fullPreviewLines(); - const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => { - const thisSize = i === 0 ? - getFirstLinePrefix().length : - getOtherLinePrefix(i).length; - - return Math.max(thisSize, largest); - }, 0); - - const formattedLines = fullMatchLines - .map((line, i) => { - const prefix = i === 0 ? - getFirstLinePrefix() : - getOtherLinePrefix(i); - - const paddingStr = ' '.repeat(largestPrefixSize - prefix.length); - const indentStr = ' '.repeat(indent); - return `${indentStr}${prefix}: ${paddingStr}${line}`; - }); - - return formattedLines.join('\n'); -} -function fileFolderMatchToString(match: FileMatch | FolderMatch | FolderMatchWithResource, labelService: ILabelService): { text: string; count: number } { - if (match instanceof FileMatch) { - return fileMatchToString(match, labelService); - } else { - return folderMatchToString(match, labelService); - } -} -const lineDelimiter = isWindows ? '\r\n' : '\n'; -function fileMatchToString(fileMatch: FileMatch, labelService: ILabelService): { text: string; count: number } { - const matchTextRows = fileMatch.matches() - .sort(searchMatchComparer) - .map(match => matchToString(match, 2)); - const uriString = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); - return { - text: `${uriString}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, - count: matchTextRows.length - }; -} - -function folderMatchToString(folderMatch: FolderMatchWithResource | FolderMatch, labelService: ILabelService): { text: string; count: number } { - const results: string[] = []; - let numMatches = 0; - - const matches = folderMatch.matches().sort(searchMatchComparer); - - matches.forEach(match => { - const result = fileFolderMatchToString(match, labelService); - numMatches += result.count; - results.push(result.text); - }); - - return { - text: results.join(lineDelimiter + lineDelimiter), - count: numMatches - }; -} - -export const copyMatchCommand: ICommandHandler = async (accessor, match: RenderableMatch | undefined) => { - if (!match) { - const selection = getSelectedRow(accessor); - if (!selection) { - return; - } - - match = selection; - } - - const clipboardService = accessor.get(IClipboardService); - const labelService = accessor.get(ILabelService); - - let text: string | undefined; - if (match instanceof Match) { - text = matchToString(match); - } else if (match instanceof FileMatch) { - text = fileMatchToString(match, labelService).text; - } else if (match instanceof FolderMatch) { - text = folderMatchToString(match, labelService).text; - } - - if (text) { - await clipboardService.writeText(text); - } -}; - -function allFolderMatchesToString(folderMatches: Array, labelService: ILabelService): string { - const folderResults: string[] = []; - folderMatches = folderMatches.sort(searchMatchComparer); - for (let i = 0; i < folderMatches.length; i++) { - const folderResult = folderMatchToString(folderMatches[i], labelService); - if (folderResult.count) { - folderResults.push(folderResult.text); - } - } - - return folderResults.join(lineDelimiter + lineDelimiter); -} - -function getSelectedRow(accessor: ServicesAccessor): RenderableMatch | undefined | null { - const viewsService = accessor.get(IViewsService); - const searchView = getSearchView(viewsService); - return searchView?.getControl().getSelection()[0]; -} - -export const copyAllCommand: ICommandHandler = async (accessor) => { - const viewsService = accessor.get(IViewsService); - const clipboardService = accessor.get(IClipboardService); - const labelService = accessor.get(ILabelService); - - const searchView = getSearchView(viewsService); - if (searchView) { - const root = searchView.searchResult; - - const text = allFolderMatchesToString(root.folderMatches(), labelService); - await clipboardService.writeText(text); - } -}; - -export const clearHistoryCommand: ICommandHandler = accessor => { - const searchHistoryService = accessor.get(ISearchHistoryService); - searchHistoryService.clearHistory(); -}; - -export const focusSearchListCommand: ICommandHandler = accessor => { - const viewsService = accessor.get(IViewsService); - openSearchView(viewsService).then(searchView => { - searchView?.moveFocusToResults(); - }); -}; - -export function getMultiSelectedSearchResources(viewer: WorkbenchCompressibleObjectTree, currElement: RenderableMatch | undefined, sortConfig: ISearchConfigurationProperties): URI[] { - return getElementsToOperateOnInfo(viewer, currElement, sortConfig).elements - .map((renderableMatch) => ((renderableMatch instanceof Match) ? null : renderableMatch.resource)) - .filter((renderableMatch): renderableMatch is URI => (renderableMatch !== null)); -} - -function getElementsToOperateOnInfo(viewer: WorkbenchCompressibleObjectTree, currElement: RenderableMatch | undefined, sortConfig: ISearchConfigurationProperties): { elements: RenderableMatch[]; mustReselect: boolean } { - let elements: RenderableMatch[] = viewer.getSelection().filter((x): x is RenderableMatch => x !== null).sort((a, b) => searchComparer(a, b, sortConfig.sortOrder)); - - const mustReselect = !currElement || elements.includes(currElement); // this indicates whether we need to re-focus/re-select on a remove. - - // if selection doesn't include multiple elements, just return current focus element. - if (currElement && !(elements.length > 1 && elements.includes(currElement))) { - elements = [currElement]; - } - - return { elements, mustReselect }; -} - - -function compareLevels(elem1: RenderableMatch, elem2: RenderableMatch) { - if (elem1 instanceof Match) { - if (elem2 instanceof Match) { - return 0; - } else { - return -1; - } - - } else if (elem1 instanceof FileMatch) { - if (elem2 instanceof Match) { - return 1; - } else if (elem2 instanceof FileMatch) { - return 0; - } else { - return -1; - } - - } else { - // FolderMatch - if (elem2 instanceof FolderMatch) { - return 0; - } else { - return 1; - } - } -} - -/** - * Returns element to focus after removing the given element - */ -export function getElementToFocusAfterRemoved(viewer: WorkbenchCompressibleObjectTree, element: RenderableMatch, elementsToRemove: RenderableMatch[]): RenderableMatch | undefined { - const navigator: ITreeNavigator = viewer.navigate(element); - if (element instanceof FolderMatch) { - while (!!navigator.next() && (!(navigator.current() instanceof FolderMatch) || arrayContainsElementOrParent(navigator.current(), elementsToRemove))) { } - } else if (element instanceof FileMatch) { - while (!!navigator.next() && (!(navigator.current() instanceof FileMatch) || arrayContainsElementOrParent(navigator.current(), elementsToRemove))) { - viewer.expand(navigator.current()); - } - } else { - while (navigator.next() && (!(navigator.current() instanceof Match) || arrayContainsElementOrParent(navigator.current(), elementsToRemove))) { - viewer.expand(navigator.current()); - } - } - return navigator.current(); -} - -/*** - * Finds the last element in the tree with the same type as `element` - */ -export function getLastNodeFromSameType(viewer: WorkbenchCompressibleObjectTree, element: RenderableMatch): RenderableMatch | undefined { - let lastElem: RenderableMatch | null = viewer.lastVisibleElement ?? null; - - while (lastElem) { - const compareVal = compareLevels(element, lastElem); - if (compareVal === -1) { - viewer.expand(lastElem); - lastElem = viewer.lastVisibleElement; - } else if (compareVal === 1) { - lastElem = viewer.getParentElement(lastElem); - } else { - return lastElem; - } - } - - return undefined; -} diff --git a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts new file mode 100644 index 00000000000..932b714e3ce --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { createKeybinding, ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { OS } from 'vs/base/common/platform'; +import * as nls from 'vs/nls'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { IViewsService } from 'vs/workbench/common/views'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { RenderableMatch, searchComparer } from 'vs/workbench/contrib/search/common/searchModel'; +import { ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; + +export const category = { value: nls.localize('search', "Search"), original: 'Search' }; + +export function isSearchViewFocused(viewsService: IViewsService): boolean { + const searchView = getSearchView(viewsService); + const activeElement = document.activeElement; + return !!(searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer())); +} + +export function appendKeyBindingLabel(label: string, inputKeyBinding: number | ResolvedKeybinding | undefined, keyBindingService2: IKeybindingService): string { + if (typeof inputKeyBinding === 'number') { + const keybinding = createKeybinding(inputKeyBinding, OS); + if (keybinding) { + const resolvedKeybindings = keyBindingService2.resolveKeybinding(keybinding); + return doAppendKeyBindingLabel(label, resolvedKeybindings.length > 0 ? resolvedKeybindings[0] : undefined); + } + return doAppendKeyBindingLabel(label, undefined); + } else { + return doAppendKeyBindingLabel(label, inputKeyBinding); + } +} + +export function getSearchView(viewsService: IViewsService): SearchView | undefined { + return viewsService.getActiveViewWithId(VIEW_ID) as SearchView; +} + +export function getElementsToOperateOnInfo(viewer: WorkbenchCompressibleObjectTree, currElement: RenderableMatch | undefined, sortConfig: ISearchConfigurationProperties): { elements: RenderableMatch[]; mustReselect: boolean } { + let elements: RenderableMatch[] = viewer.getSelection().filter((x): x is RenderableMatch => x !== null).sort((a, b) => searchComparer(a, b, sortConfig.sortOrder)); + + const mustReselect = !currElement || elements.includes(currElement); // this indicates whether we need to re-focus/re-select on a remove. + + // if selection doesn't include multiple elements, just return current focus element. + if (currElement && !(elements.length > 1 && elements.includes(currElement))) { + elements = [currElement]; + } + + return { elements, mustReselect }; +} + +export function openSearchView(viewsService: IViewsService, focus?: boolean): Promise { + return viewsService.openView(VIEW_ID, focus).then(view => (view as SearchView ?? undefined)); +} + +function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding | undefined): string { + return keyBinding ? label + ' (' + keyBinding.getLabel() + ')' : label; +} + diff --git a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts new file mode 100644 index 00000000000..58db3b759d5 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts @@ -0,0 +1,256 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IViewsService } from 'vs/workbench/common/views'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer } from 'vs/workbench/contrib/search/common/searchModel'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { category, getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; +import { isWindows } from 'vs/base/common/platform'; + +//#region Actions +registerAction2(class CopyMatchCommandAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.CopyMatchCommandId, + title: { + value: nls.localize('copyMatchLabel', "Copy"), + original: 'Copy' + }, + category: category.value, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.FileMatchOrMatchFocusKey, + primary: KeyMod.CtrlCmd | KeyCode.KeyC, + }, + menu: [{ + id: MenuId.SearchContext, + when: Constants.FileMatchOrMatchFocusKey, + group: 'search_2', + order: 1 + }] + }); + + } + + override async run(accessor: ServicesAccessor, match: RenderableMatch | undefined): Promise { + await copyMatchCommand(accessor, match); + } +}); + +registerAction2(class CopyPathCommandAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.CopyPathCommandId, + title: { + value: nls.localize('copyPathLabel', "Copy Path"), + original: 'Copy Path' + }, + category: category.value, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.FileMatchOrFolderMatchWithResourceFocusKey, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC + }, + }, + menu: [{ + id: MenuId.SearchContext, + when: Constants.FileMatchOrFolderMatchWithResourceFocusKey, + group: 'search_2', + order: 2 + }] + }); + + } + + override async run(accessor: ServicesAccessor, fileMatch: FileMatch | FolderMatchWithResource | undefined): Promise { + await copyPathCommand(accessor, fileMatch); + } +}); + +registerAction2(class CopyAllCommandAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.CopyAllCommandId, + title: { + value: nls.localize('copyAllLabel', "Copy All"), + original: 'Copy All' + }, + category: category.value, + menu: [{ + id: MenuId.SearchContext, + when: Constants.HasSearchResults, + group: 'search_2', + order: 3 + }] + }); + + } + + override async run(accessor: ServicesAccessor): Promise { + await copyAllCommand(accessor); + } +}); + +//#endregion + +//#region Helpers +export const lineDelimiter = isWindows ? '\r\n' : '\n'; + +async function copyPathCommand(accessor: ServicesAccessor, fileMatch: FileMatch | FolderMatchWithResource | undefined) { + if (!fileMatch) { + const selection = getSelectedRow(accessor); + if (!(selection instanceof FileMatch || selection instanceof FolderMatchWithResource)) { + return; + } + + fileMatch = selection; + } + + const clipboardService = accessor.get(IClipboardService); + const labelService = accessor.get(ILabelService); + + const text = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); + await clipboardService.writeText(text); +} + +async function copyMatchCommand(accessor: ServicesAccessor, match: RenderableMatch | undefined) { + if (!match) { + const selection = getSelectedRow(accessor); + if (!selection) { + return; + } + + match = selection; + } + + const clipboardService = accessor.get(IClipboardService); + const labelService = accessor.get(ILabelService); + + let text: string | undefined; + if (match instanceof Match) { + text = matchToString(match); + } else if (match instanceof FileMatch) { + text = fileMatchToString(match, labelService).text; + } else if (match instanceof FolderMatch) { + text = folderMatchToString(match, labelService).text; + } + + if (text) { + await clipboardService.writeText(text); + } +} + +async function copyAllCommand(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const clipboardService = accessor.get(IClipboardService); + const labelService = accessor.get(ILabelService); + + const searchView = getSearchView(viewsService); + if (searchView) { + const root = searchView.searchResult; + + const text = allFolderMatchesToString(root.folderMatches(), labelService); + await clipboardService.writeText(text); + } +} + +function matchToString(match: Match, indent = 0): string { + const getFirstLinePrefix = () => `${match.range().startLineNumber},${match.range().startColumn}`; + const getOtherLinePrefix = (i: number) => match.range().startLineNumber + i + ''; + + const fullMatchLines = match.fullPreviewLines(); + const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => { + const thisSize = i === 0 ? + getFirstLinePrefix().length : + getOtherLinePrefix(i).length; + + return Math.max(thisSize, largest); + }, 0); + + const formattedLines = fullMatchLines + .map((line, i) => { + const prefix = i === 0 ? + getFirstLinePrefix() : + getOtherLinePrefix(i); + + const paddingStr = ' '.repeat(largestPrefixSize - prefix.length); + const indentStr = ' '.repeat(indent); + return `${indentStr}${prefix}: ${paddingStr}${line}`; + }); + + return formattedLines.join('\n'); +} + +function fileFolderMatchToString(match: FileMatch | FolderMatch | FolderMatchWithResource, labelService: ILabelService): { text: string; count: number } { + if (match instanceof FileMatch) { + return fileMatchToString(match, labelService); + } else { + return folderMatchToString(match, labelService); + } +} + +function fileMatchToString(fileMatch: FileMatch, labelService: ILabelService): { text: string; count: number } { + const matchTextRows = fileMatch.matches() + .sort(searchMatchComparer) + .map(match => matchToString(match, 2)); + const uriString = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); + return { + text: `${uriString}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, + count: matchTextRows.length + }; +} + +function folderMatchToString(folderMatch: FolderMatchWithResource | FolderMatch, labelService: ILabelService): { text: string; count: number } { + const results: string[] = []; + let numMatches = 0; + + const matches = folderMatch.matches().sort(searchMatchComparer); + + matches.forEach(match => { + const result = fileFolderMatchToString(match, labelService); + numMatches += result.count; + results.push(result.text); + }); + + return { + text: results.join(lineDelimiter + lineDelimiter), + count: numMatches + }; +} + +function allFolderMatchesToString(folderMatches: Array, labelService: ILabelService): string { + const folderResults: string[] = []; + folderMatches = folderMatches.sort(searchMatchComparer); + for (let i = 0; i < folderMatches.length; i++) { + const folderResult = folderMatchToString(folderMatches[i], labelService); + if (folderResult.count) { + folderResults.push(folderResult.text); + } + } + + return folderResults.join(lineDelimiter + lineDelimiter); +} + +function getSelectedRow(accessor: ServicesAccessor): RenderableMatch | undefined | null { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + return searchView?.getControl().getSelection()[0]; +} + +//#endregion diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts new file mode 100644 index 00000000000..2d8cf8d9929 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -0,0 +1,384 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { dirname } from 'vs/base/common/resources'; +import * as nls from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IListService, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { FileMatch, FolderMatchWithResource, Match, RenderableMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { URI } from 'vs/base/common/uri'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { resolveResourcesForSearchIncludes } from 'vs/workbench/services/search/common/queryBuilder'; +import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; +import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { category, getElementsToOperateOnInfo, getSearchView, openSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; + + +//#region Interfaces +export interface IFindInFilesArgs { + query?: string; + replace?: string; + preserveCase?: boolean; + triggerSearch?: boolean; + filesToInclude?: string; + filesToExclude?: string; + isRegex?: boolean; + isCaseSensitive?: boolean; + matchWholeWord?: boolean; + useExcludeSettingsAndIgnoreFiles?: boolean; + onlyOpenEditors?: boolean; +} +//#endregion + +registerAction2(class RestrictSearchToFolderAction extends Action2 { + constructor() { + super({ + id: Constants.RestrictSearchToFolderId, + title: { + value: nls.localize('restrictResultsToFolder', "Restrict Search to Folder"), + original: 'Restrict Search to Folder' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ResourceFolderFocusKey), + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyF, + }, + menu: [ + { + id: MenuId.SearchContext, + group: 'search', + order: 3, + when: ContextKeyExpr.and(Constants.ResourceFolderFocusKey) + } + ] + }); + } + async run(accessor: ServicesAccessor, folderMatch?: FolderMatchWithResource) { + await searchWithFolderCommand(accessor, false, true, undefined, folderMatch); + } +}); + +registerAction2(class ExcludeFolderFromSearchAction extends Action2 { + constructor() { + super({ + id: Constants.ExcludeFolderFromSearchId, + title: { + value: nls.localize('excludeFolderFromSearch', "Exclude Folder from Search"), + original: 'Exclude Folder from Search' + }, + category, + menu: [ + { + id: MenuId.SearchContext, + group: 'search', + order: 4, + when: ContextKeyExpr.and(Constants.ResourceFolderFocusKey) + } + ] + }); + } + async run(accessor: ServicesAccessor, folderMatch?: FolderMatchWithResource) { + await searchWithFolderCommand(accessor, false, false, undefined, folderMatch); + } +}); + +registerAction2(class RevealInSideBarForSearchResultsAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.RevealInSideBarForSearchResults, + title: { + value: nls.localize('revealInSideBar', "Reveal in Explorer View"), + original: 'Reveal in Explorer View' + }, + category: category, + menu: [{ + id: MenuId.SearchContext, + when: ContextKeyExpr.and(Constants.FileFocusKey, Constants.HasSearchResults), + group: 'search_3', + order: 1 + }] + }); + + } + + override async run(accessor: ServicesAccessor, args: any): Promise { + const paneCompositeService = accessor.get(IPaneCompositePartService); + const explorerService = accessor.get(IExplorerService); + const contextService = accessor.get(IWorkspaceContextService); + + const searchView = getSearchView(accessor.get(IViewsService)); + if (!searchView) { + return; + } + + let fileMatch: FileMatch; + if (!(args instanceof FileMatch)) { + args = searchView.getControl().getFocus()[0]; + } + if (args instanceof FileMatch) { + fileMatch = args; + } else { + return; + } + + paneCompositeService.openPaneComposite(VIEWLET_ID_FILES, ViewContainerLocation.Sidebar, false).then((viewlet) => { + if (!viewlet) { + return; + } + + const explorerViewContainer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer; + const uri = fileMatch.resource; + if (uri && contextService.isInsideWorkspace(uri)) { + const explorerView = explorerViewContainer.getExplorerView(); + explorerView.setExpanded(true); + explorerService.select(uri, true).then(() => explorerView.focus(), onUnexpectedError); + } + }); + } +}); + +// Find in Files by default is the same as View: Show Search, but can be configured to open a search editor instead with the `search.mode` binding +registerAction2(class FindInFilesAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.FindInFilesActionId, + title: { + value: nls.localize('findInFiles', "Find in Files"), + mnemonicTitle: nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), + original: 'Find in Files' + }, + description: { + description: nls.localize('findInFiles.description', "Open a workspace search"), + args: [ + { + name: nls.localize('findInFiles.args', "A set of options for the search"), + schema: { + type: 'object', + properties: { + query: { 'type': 'string' }, + replace: { 'type': 'string' }, + preserveCase: { 'type': 'boolean' }, + triggerSearch: { 'type': 'boolean' }, + filesToInclude: { 'type': 'string' }, + filesToExclude: { 'type': 'string' }, + isRegex: { 'type': 'boolean' }, + isCaseSensitive: { 'type': 'boolean' }, + matchWholeWord: { 'type': 'boolean' }, + useExcludeSettingsAndIgnoreFiles: { 'type': 'boolean' }, + onlyOpenEditors: { 'type': 'boolean' }, + } + } + }, + ] + }, + category: category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyF, + }, + menu: [{ + id: MenuId.MenubarEditMenu, + group: '4_find_global', + order: 1, + }], + f1: true + }); + + } + + override async run(accessor: ServicesAccessor, args: IFindInFilesArgs = {}): Promise { + findInFilesCommand(accessor, args); + } +}); + +registerAction2(class FindInFolderAction extends Action2 { + // from explorer + constructor() { + super({ + id: Constants.FindInFolderId, + title: { + value: nls.localize('findInFolder', "Find in Folder..."), + original: 'Find in Folder...' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext), + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyF, + }, + menu: [ + { + id: MenuId.ExplorerContext, + group: '4_search', + order: 10, + when: ContextKeyExpr.and(ExplorerFolderContext) + } + ] + }); + } + async run(accessor: ServicesAccessor, resource?: URI) { + await searchWithFolderCommand(accessor, true, true, resource); + } +}); + +registerAction2(class FindInWorkspaceAction extends Action2 { + // from explorer + constructor() { + super({ + id: Constants.FindInWorkspaceId, + title: { + value: nls.localize('findInWorkspace', "Find in Workspace..."), + original: 'Find in Workspace...' + }, + category, + menu: [ + { + id: MenuId.ExplorerContext, + group: '4_search', + order: 10, + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext.toNegated()) + + } + ] + }); + } + async run(accessor: ServicesAccessor) { + const searchConfig = accessor.get(IConfigurationService).getValue().search; + const mode = searchConfig.mode; + + if (mode === 'view') { + const searchView = await openSearchView(accessor.get(IViewsService), true); + searchView?.searchInFolders(); + } + else { + return accessor.get(ICommandService).executeCommand(SearchEditorConstants.OpenEditorCommandId, { + location: mode === 'newEditor' ? 'new' : 'reuse', + filesToInclude: '', + }); + } + } +}); + +//#region Helpers +async function searchWithFolderCommand(accessor: ServicesAccessor, isFromExplorer: boolean, isIncludes: boolean, resource?: URI, folderMatch?: FolderMatchWithResource) { + const listService = accessor.get(IListService); + const fileService = accessor.get(IFileService); + const viewsService = accessor.get(IViewsService); + const contextService = accessor.get(IWorkspaceContextService); + const commandService = accessor.get(ICommandService); + const searchConfig = accessor.get(IConfigurationService).getValue().search; + const mode = searchConfig.mode; + + let resources: URI[]; + + if (isFromExplorer) { + resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IExplorerService)); + } else { + const searchView = getSearchView(accessor.get(IViewsService)); + if (!searchView) { + return; + } + resources = getMultiSelectedSearchResources(searchView.getControl(), folderMatch, searchConfig); + } + + const resolvedResources = fileService.resolveAll(resources.map(resource => ({ resource }))).then(results => { + const folders: URI[] = []; + results.forEach(result => { + if (result.success && result.stat) { + folders.push(result.stat.isDirectory ? result.stat.resource : dirname(result.stat.resource)); + } + }); + return resolveResourcesForSearchIncludes(folders, contextService); + }); + + if (mode === 'view') { + const searchView = await openSearchView(viewsService, true); + if (resources && resources.length && searchView) { + if (isIncludes) { + searchView.searchInFolders(await resolvedResources); + } else { + searchView.searchOutsideOfFolders(await resolvedResources); + } + } + return undefined; + } else { + if (isIncludes) { + return commandService.executeCommand(SearchEditorConstants.OpenEditorCommandId, { + filesToInclude: (await resolvedResources).join(', '), + showIncludesExcludes: true, + location: mode === 'newEditor' ? 'new' : 'reuse', + }); + } + else { + return commandService.executeCommand(SearchEditorConstants.OpenEditorCommandId, { + filesToExclude: (await resolvedResources).join(', '), + showIncludesExcludes: true, + location: mode === 'newEditor' ? 'new' : 'reuse', + }); + } + } +} + +function getMultiSelectedSearchResources(viewer: WorkbenchCompressibleObjectTree, currElement: RenderableMatch | undefined, sortConfig: ISearchConfigurationProperties): URI[] { + return getElementsToOperateOnInfo(viewer, currElement, sortConfig).elements + .map((renderableMatch) => ((renderableMatch instanceof Match) ? null : renderableMatch.resource)) + .filter((renderableMatch): renderableMatch is URI => (renderableMatch !== null)); +} + +export function findInFilesCommand(accessor: ServicesAccessor, args: IFindInFilesArgs = {}) { + const searchConfig = accessor.get(IConfigurationService).getValue().search; + const mode = searchConfig.mode; + if (mode === 'view') { + const viewsService = accessor.get(IViewsService); + openSearchView(viewsService, false).then(openedView => { + if (openedView) { + const searchAndReplaceWidget = openedView.searchAndReplaceWidget; + searchAndReplaceWidget.toggleReplace(typeof args.replace === 'string'); + let updatedText = false; + if (typeof args.query === 'string') { + openedView.setSearchParameters(args); + } else { + updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: typeof args.replace !== 'string' }); + } + openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); + } + }); + } else { + const convertArgs = (args: IFindInFilesArgs): OpenSearchEditorArgs => ({ + location: mode === 'newEditor' ? 'new' : 'reuse', + query: args.query, + filesToInclude: args.filesToInclude, + filesToExclude: args.filesToExclude, + matchWholeWord: args.matchWholeWord, + isCaseSensitive: args.isCaseSensitive, + isRegexp: args.isRegex, + useExcludeSettingsAndIgnoreFiles: args.useExcludeSettingsAndIgnoreFiles, + onlyOpenEditors: args.onlyOpenEditors, + showIncludesExcludes: !!(args.filesToExclude || args.filesToExclude || !args.useExcludeSettingsAndIgnoreFiles), + }); + accessor.get(ICommandService).executeCommand(SearchEditorConstants.OpenEditorCommandId, convertArgs(args)); + } +} +//#endregion diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts new file mode 100644 index 00000000000..bc0f99679f8 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -0,0 +1,533 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isMacintosh } from 'vs/base/common/platform'; +import * as nls from 'vs/nls'; +import { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { IViewsService } from 'vs/workbench/common/views'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { FileMatchOrMatch, FolderMatch, RenderableMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; +import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { assertIsDefined } from 'vs/base/common/types'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/browser/findModel'; +import { category, getSearchView, openSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; + +//#region Actions: Changing Search Input Options +registerAction2(class ToggleQueryDetailsAction extends Action2 { + constructor() { + super({ + id: Constants.ToggleQueryDetailsActionId, + title: { + value: nls.localize('ToggleQueryDetailsAction.label', "Toggle Query Details"), + original: 'Toggle Query Details' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.or(Constants.SearchViewFocusedKey, SearchEditorConstants.InSearchEditor), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyJ, + }, + }); + } + run(accessor: ServicesAccessor) { + const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); + if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { + (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(); + } else if (contextService.getValue(Constants.SearchViewFocusedKey.serialize())) { + const searchView = getSearchView(accessor.get(IViewsService)); + assertIsDefined(searchView).toggleQueryDetails(); + } + } +}); + +registerAction2(class CloseReplaceAction extends Action2 { + constructor() { + super({ + id: Constants.CloseReplaceWidgetActionId, + title: { + value: nls.localize('CloseReplaceWidget.label', "Close Replace Widget"), + original: 'Close Replace Widget' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey), + primary: KeyCode.Escape, + }, + }); + } + run(accessor: ServicesAccessor) { + + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + searchView.searchAndReplaceWidget.toggleReplace(false); + searchView.searchAndReplaceWidget.focus(); + } + return Promise.resolve(null); + } +}); + +registerAction2(class ToggleCaseSensitiveCommandAction extends Action2 { + + constructor( + ) { + + super({ + id: Constants.ToggleCaseSensitiveCommandId, + title: { + value: nls.localize('ToggleCaseSensitiveCommandId.label', "Toggle Case Sensitive"), + original: 'Toggle Case Sensitive' + }, + category: category, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: isMacintosh ? ContextKeyExpr.and(Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()) : Constants.SearchViewFocusedKey, + }, ToggleCaseSensitiveKeybinding) + + }); + + } + + override async run(accessor: ServicesAccessor): Promise { + toggleCaseSensitiveCommand(accessor); + } +}); + +registerAction2(class ToggleWholeWordCommandAction extends Action2 { + constructor() { + super({ + id: Constants.ToggleWholeWordCommandId, + title: { + value: nls.localize('ToggleWholeWordCommandId.label', 'Toggle Whole Word'), + original: 'Toggle Whole Word' + }, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.SearchViewFocusedKey, + }, ToggleWholeWordKeybinding), + category: category.value, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + return toggleWholeWordCommand(accessor); + } +}); + +registerAction2(class ToggleRegexCommandAction extends Action2 { + constructor() { + super({ + id: Constants.ToggleRegexCommandId, + title: { + value: nls.localize('ToggleRegexCommandId.label', 'Toggle Regex'), + original: 'Toggle Regex' + }, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.SearchViewFocusedKey, + }, ToggleRegexKeybinding), + category: category.value, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + return toggleRegexCommand(accessor); + } +}); + +registerAction2(class TogglePreserveCaseAction extends Action2 { + constructor() { + super({ + id: Constants.TogglePreserveCaseId, + title: { + value: nls.localize('TogglePreserveCaseId.label', 'Toggle Preserve Case'), + original: 'Toggle Preserve Case' + }, + keybinding: Object.assign({ + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.SearchViewFocusedKey, + }, TogglePreserveCaseKeybinding), + category: category.value, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + return togglePreserveCaseCommand(accessor); + } +}); + +//#endregion +//#region Actions: Opening Matches +registerAction2(class OpenMatchAction extends Action2 { + constructor() { + super({ + id: Constants.OpenMatch, + title: { + value: nls.localize('OpenMatch.label', "Open Match"), + original: 'Open Match' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + primary: KeyCode.Enter, + mac: { + primary: KeyCode.Enter, + secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow] + }, + }, + }); + } + run(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); + const viewer = searchView.getControl(); + const focus = tree.getFocus()[0]; + + if (focus instanceof FolderMatch) { + viewer.toggleCollapsed(focus); + } else { + searchView.open(tree.getFocus()[0], false, false, true); + } + } + } +}); + +registerAction2(class OpenMatchToSideAction extends Action2 { + constructor() { + super({ + id: Constants.OpenMatchToSide, + title: { + value: nls.localize('OpenMatchToSide.label', "Open Match To Side"), + original: 'Open Match To Side' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { + primary: KeyMod.WinCtrl | KeyCode.Enter + }, + }, + }); + } + run(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); + searchView.open(tree.getFocus()[0], false, true, true); + } + } +}); + +registerAction2(class AddCursorsAtSearchResultsAction extends Action2 { + constructor() { + super({ + id: Constants.AddCursorsAtSearchResults, + title: { + value: nls.localize('AddCursorsAtSearchResults.label', 'Add Cursors at Search Results'), + original: 'Add Cursors at Search Results' + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL, + }, + category: category.value, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + const tree: WorkbenchCompressibleObjectTree = searchView.getControl(); + searchView.openEditorWithMultiCursor(tree.getFocus()[0]); + } + } +}); + +//#endregion +//#region Actions: Toggling Focus +registerAction2(class FocusNextInputAction extends Action2 { + constructor() { + super({ + id: Constants.FocusNextInputActionId, + title: { + value: nls.localize('FocusNextInputAction.label', "Focus Next Input"), + original: 'Focus Next Input' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.or( + ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), + ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey)), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + }, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + // cast as we cannot import SearchEditor as a value b/c cyclic dependency. + (editorService.activeEditorPane as SearchEditor).focusNextInput(); + } + + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.focusNextInputBox(); + } +}); + +registerAction2(class FocusPreviousInputAction extends Action2 { + constructor() { + super({ + id: Constants.FocusPreviousInputActionId, + title: { + value: nls.localize('FocusPreviousInputAction.label', "Focus Previous Input"), + original: 'Focus Previous Input' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.or( + ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), + ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey, Constants.SearchInputBoxFocusedKey.toNegated())), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + }, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + // cast as we cannot import SearchEditor as a value b/c cyclic dependency. + (editorService.activeEditorPane as SearchEditor).focusPrevInput(); + } + + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.focusPreviousInputBox(); + } +}); + +registerAction2(class FocusSearchFromResultsAction extends Action2 { + constructor() { + super({ + id: Constants.FocusSearchFromResults, + title: { + value: nls.localize('FocusSearchFromResults.label', "Focus Search From Results"), + original: 'Focus Search From Results' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FirstMatchFocusKey), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + }, + }); + } + run(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.focusPreviousInputBox(); + } +}); + +registerAction2(class ToggleSearchOnTypeAction extends Action2 { + private static readonly searchOnTypeKey = 'search.searchOnType'; + + constructor( + ) { + super({ + id: Constants.ToggleSearchOnTypeActionId, + title: { + value: nls.localize('toggleTabs', 'Toggle Search on Type'), + original: 'Toggle Search on Type' + }, + category: category.value, + }); + + } + + override async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + const searchOnType = configurationService.getValue(ToggleSearchOnTypeAction.searchOnTypeKey); + return configurationService.updateValue(ToggleSearchOnTypeAction.searchOnTypeKey, !searchOnType); + } +}); + +registerAction2(class FocusSearchListCommandAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.FocusSearchListCommandID, + title: { + value: nls.localize('focusSearchListCommandLabel', "Focus List"), + original: 'Focus List' + }, + category: category, + f1: true + }); + } + + override async run(accessor: ServicesAccessor): Promise { + focusSearchListCommand(accessor); + } +}); + +registerAction2(class FocusNextSearchResultAction extends Action2 { + constructor() { + super({ + id: Constants.FocusNextSearchResultActionId, + title: { + value: nls.localize('FocusNextSearchResult.label', 'Focus Next Search Result'), + original: 'Focus Next Search Result' + }, + keybinding: [{ + primary: KeyCode.F4, + weight: KeybindingWeight.WorkbenchContrib, + }], + category: category.value, + + precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), + }); + } + + override async run(accessor: ServicesAccessor): Promise { + return await focusNextSearchResult(accessor); + } +}); + +registerAction2(class FocusPreviousSearchResultAction extends Action2 { + constructor() { + super({ + id: Constants.FocusPreviousSearchResultActionId, + title: { + value: nls.localize('FocusPreviousSearchResult.label', 'Search: Focus Previous Search Result'), + original: 'Search: Focus Previous Search Result' + }, + keybinding: [{ + primary: KeyMod.Shift | KeyCode.F4, + weight: KeybindingWeight.WorkbenchContrib, + }], + category: category.value, + + precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), + }); + } + + override async run(accessor: ServicesAccessor): Promise { + return await focusPreviousSearchResult(accessor); + } +}); + +registerAction2(class ReplaceInFilesAction extends Action2 { + constructor() { + super({ + id: Constants.ReplaceInFilesActionId, + title: { + value: nls.localize('replaceInFiles', 'Search: Replace in Files'), + original: 'Search: Replace in Files' + }, + keybinding: [{ + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyH, + weight: KeybindingWeight.WorkbenchContrib, + }], + category: category.value, + menu: [{ + id: MenuId.MenubarEditMenu, + group: '4_find_global', + order: 2 + }], + }); + } + + override async run(accessor: ServicesAccessor): Promise { + return await findOrReplaceInFiles(accessor, true); + } +}); + +//#endregion + +//#region Helpers +function toggleCaseSensitiveCommand(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.toggleCaseSensitive(); +} + +function toggleWholeWordCommand(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.toggleWholeWords(); +} + +function toggleRegexCommand(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.toggleRegex(); +} + +function togglePreserveCaseCommand(accessor: ServicesAccessor) { + const searchView = getSearchView(accessor.get(IViewsService)); + searchView?.togglePreserveCase(); +} + +const focusSearchListCommand: ICommandHandler = accessor => { + const viewsService = accessor.get(IViewsService); + openSearchView(viewsService).then(searchView => { + searchView?.moveFocusToResults(); + }); +}; + +async function focusNextSearchResult(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + // cast as we cannot import SearchEditor as a value b/c cyclic dependency. + return (editorService.activeEditorPane as SearchEditor).focusNextResult(); + } + + return openSearchView(accessor.get(IViewsService)).then(searchView => { + searchView?.selectNextMatch(); + }); +} + +async function focusPreviousSearchResult(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + // cast as we cannot import SearchEditor as a value b/c cyclic dependency. + return (editorService.activeEditorPane as SearchEditor).focusPreviousResult(); + } + + return openSearchView(accessor.get(IViewsService)).then(searchView => { + searchView?.selectPreviousMatch(); + }); +} + +async function findOrReplaceInFiles(accessor: ServicesAccessor, expandSearchReplaceWidget: boolean): Promise { + return openSearchView(accessor.get(IViewsService), false).then(openedView => { + if (openedView) { + const searchAndReplaceWidget = openedView.searchAndReplaceWidget; + searchAndReplaceWidget.toggleReplace(expandSearchReplaceWidget); + + const updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: !expandSearchReplaceWidget }); + openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); + } + }); +} +//#endregion diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts new file mode 100644 index 00000000000..857f868b810 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -0,0 +1,408 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getSelectionKeyboardEvent, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { IViewsService } from 'vs/workbench/common/views'; +import { searchRemoveIcon, searchReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { category, getElementsToOperateOnInfo, getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; + + +//#region Interfaces +export interface ISearchActionContext { + readonly viewer: WorkbenchCompressibleObjectTree; + readonly element: RenderableMatch; +} + + +export interface IFindInFilesArgs { + query?: string; + replace?: string; + preserveCase?: boolean; + triggerSearch?: boolean; + filesToInclude?: string; + filesToExclude?: string; + isRegex?: boolean; + isCaseSensitive?: boolean; + matchWholeWord?: boolean; + useExcludeSettingsAndIgnoreFiles?: boolean; + onlyOpenEditors?: boolean; +} + +//#endregion + +//#region Actions +registerAction2(class RemoveAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.RemoveActionId, + title: { + value: nls.localize('RemoveAction.label', "Dismiss"), + original: 'Dismiss' + }, + category, + icon: searchRemoveIcon, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + primary: KeyCode.Delete, + mac: { + primary: KeyMod.CtrlCmd | KeyCode.Backspace, + }, + }, + menu: [ + { + id: MenuId.SearchContext, + group: 'search', + order: 2, + }, + { + id: MenuId.SearchActionMenu, + group: 'inline', + order: 2, + }, + ] + }); + } + + run(accessor: ServicesAccessor, context: ISearchActionContext | undefined): void { + const viewsService = accessor.get(IViewsService); + const configurationService = accessor.get(IConfigurationService); + const searchView = getSearchView(viewsService); + + if (!searchView) { + return; + } + + let element = context?.element; + let viewer = context?.viewer; + if (!viewer) { + viewer = searchView.getControl(); + } + if (!element) { + element = viewer.getFocus()[0] ?? undefined; + } + + const opInfo = getElementsToOperateOnInfo(viewer, element, configurationService.getValue('search')); + const elementsToRemove = opInfo.elements; + let focusElement = viewer.getFocus()[0] ?? undefined; + + if (elementsToRemove.length === 0) { + return; + } + + if (!focusElement || (focusElement instanceof SearchResult)) { + focusElement = element; + } + + let nextFocusElement; + if (opInfo.mustReselect && focusElement) { + nextFocusElement = getElementToFocusAfterRemoved(viewer, focusElement, elementsToRemove); + } + + const searchResult = searchView.searchResult; + + if (searchResult) { + searchResult.batchRemove(elementsToRemove); + } + + if (opInfo.mustReselect && focusElement) { + if (!nextFocusElement) { + nextFocusElement = getLastNodeFromSameType(viewer, focusElement); + } + + if (nextFocusElement && !arrayContainsElementOrParent(nextFocusElement, elementsToRemove)) { + viewer.reveal(nextFocusElement); + viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); + viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); + } + } + + viewer.domFocus(); + return; + } +}); + +registerAction2(class ReplaceAction extends Action2 { + constructor( + ) { + super({ + id: Constants.ReplaceActionId, + title: { + value: nls.localize('match.replace.label', "Replace"), + original: 'Replace' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.MatchFocusKey), + primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, + }, + icon: searchReplaceIcon, + menu: [ + { + id: MenuId.SearchContext, + when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.MatchFocusKey), + group: 'search', + order: 1 + }, + { + id: MenuId.SearchActionMenu, + when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.MatchFocusKey), + group: 'inline', + order: 1 + } + ] + }); + } + + override async run(accessor: ServicesAccessor, context: ISearchActionContext | undefined): Promise { + return performReplace(accessor, context); + } +}); + +registerAction2(class ReplaceAllAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.ReplaceAllInFileActionId, + title: { + value: nls.localize('file.replaceAll.label', "Replace All"), + original: 'Replace All' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FileFocusKey), + primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], + }, + icon: searchReplaceIcon, + menu: [ + { + id: MenuId.SearchContext, + when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FileFocusKey), + group: 'search', + order: 1 + }, + { + id: MenuId.SearchActionMenu, + when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FileFocusKey), + group: 'inline', + order: 1 + } + ] + }); + } + + override async run(accessor: ServicesAccessor, context: ISearchActionContext | undefined): Promise { + return performReplace(accessor, context); + } +}); + +registerAction2(class ReplaceAllInFolderAction extends Action2 { + constructor( + ) { + super({ + id: Constants.ReplaceAllInFolderActionId, + title: { + value: nls.localize('file.replaceAll.label', "Replace All"), + original: 'Replace All' + }, + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FolderFocusKey), + primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], + }, + icon: searchReplaceIcon, + menu: [ + { + id: MenuId.SearchContext, + when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FolderFocusKey), + group: 'search', + order: 1 + }, + { + id: MenuId.SearchActionMenu, + when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FolderFocusKey), + group: 'inline', + order: 1 + } + ] + }); + } + + override async run(accessor: ServicesAccessor, context: ISearchActionContext | undefined): Promise { + return performReplace(accessor, context); + } +}); + +//#endregion + +//#region Helpers + +function performReplace(accessor: ServicesAccessor, + context: ISearchActionContext | undefined): void { + const configurationService = accessor.get(IConfigurationService); + const viewsService = accessor.get(IViewsService); + + const viewlet: SearchView | undefined = getSearchView(viewsService); + const viewer: WorkbenchCompressibleObjectTree | undefined = context?.viewer ?? viewlet?.getControl(); + + if (!viewer) { + return; + } + const element: RenderableMatch | null = context?.element ?? viewer.getFocus()[0]; + + // since multiple elements can be selected, we need to check the type of the FolderMatch/FileMatch/Match before we perform the replace. + const opInfo = getElementsToOperateOnInfo(viewer, element ?? undefined, configurationService.getValue('search')); + const elementsToReplace = opInfo.elements; + let focusElement = viewer.getFocus()[0]; + + if (!focusElement || (focusElement && !arrayContainsElementOrParent(focusElement, elementsToReplace)) || (focusElement instanceof SearchResult)) { + focusElement = element; + } + + if (elementsToReplace.length === 0) { + return; + } + let nextFocusElement; + if (focusElement) { + nextFocusElement = getElementToFocusAfterRemoved(viewer, focusElement, elementsToReplace); + } + + const searchResult = viewlet?.searchResult; + + if (searchResult) { + searchResult.batchReplace(elementsToReplace); + } + + if (focusElement) { + if (!nextFocusElement) { + nextFocusElement = getLastNodeFromSameType(viewer, focusElement); + } + + if (nextFocusElement) { + viewer.reveal(nextFocusElement); + viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); + viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); + + if (nextFocusElement instanceof Match) { + const useReplacePreview = configurationService.getValue().search.useReplacePreview; + if (!useReplacePreview || hasToOpenFile(accessor, nextFocusElement)) { + viewlet?.open(nextFocusElement, true); + } else { + accessor.get(IReplaceService).openReplacePreview(nextFocusElement, true); + } + } else if (nextFocusElement instanceof FileMatch) { + viewlet?.open(nextFocusElement, true); + } + } + + } + + viewer.domFocus(); +} + +function hasToOpenFile(accessor: ServicesAccessor, currBottomElem: RenderableMatch): boolean { + if (!(currBottomElem instanceof Match)) { + return false; + } + const activeEditor = accessor.get(IEditorService).activeEditor; + const file = activeEditor?.resource; + if (file) { + return accessor.get(IUriIdentityService).extUri.isEqual(file, currBottomElem.parent().resource); + } + return false; +} + +function compareLevels(elem1: RenderableMatch, elem2: RenderableMatch) { + if (elem1 instanceof Match) { + if (elem2 instanceof Match) { + return 0; + } else { + return -1; + } + + } else if (elem1 instanceof FileMatch) { + if (elem2 instanceof Match) { + return 1; + } else if (elem2 instanceof FileMatch) { + return 0; + } else { + return -1; + } + + } else { + // FolderMatch + if (elem2 instanceof FolderMatch) { + return 0; + } else { + return 1; + } + } +} + +/** + * Returns element to focus after removing the given element + */ +export function getElementToFocusAfterRemoved(viewer: WorkbenchCompressibleObjectTree, element: RenderableMatch, elementsToRemove: RenderableMatch[]): RenderableMatch | undefined { + const navigator: ITreeNavigator = viewer.navigate(element); + if (element instanceof FolderMatch) { + while (!!navigator.next() && (!(navigator.current() instanceof FolderMatch) || arrayContainsElementOrParent(navigator.current(), elementsToRemove))) { } + } else if (element instanceof FileMatch) { + while (!!navigator.next() && (!(navigator.current() instanceof FileMatch) || arrayContainsElementOrParent(navigator.current(), elementsToRemove))) { + viewer.expand(navigator.current()); + } + } else { + while (navigator.next() && (!(navigator.current() instanceof Match) || arrayContainsElementOrParent(navigator.current(), elementsToRemove))) { + viewer.expand(navigator.current()); + } + } + return navigator.current(); +} + +/*** + * Finds the last element in the tree with the same type as `element` + */ +export function getLastNodeFromSameType(viewer: WorkbenchCompressibleObjectTree, element: RenderableMatch): RenderableMatch | undefined { + let lastElem: RenderableMatch | null = viewer.lastVisibleElement ?? null; + + while (lastElem) { + const compareVal = compareLevels(element, lastElem); + if (compareVal === -1) { + viewer.expand(lastElem); + lastElem = viewer.lastVisibleElement; + } else if (compareVal === 1) { + lastElem = viewer.getParentElement(lastElem); + } else { + return lastElem; + } + } + + return undefined; +} + +//#endregion + diff --git a/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts b/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts new file mode 100644 index 00000000000..e6dec6fab8a --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +//#region Actions +registerAction2(class ShowAllSymbolsAction extends Action2 { + + static readonly ID = 'workbench.action.showAllSymbols'; + static readonly LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace..."); + static readonly ALL_SYMBOLS_PREFIX = '#'; + + constructor( + ) { + super({ + id: Constants.ShowAllSymbolsActionId, + title: { + value: nls.localize('showTriggerActions', "Go to Symbol in Workspace..."), + original: 'Go to Symbol in Workspace...', + mnemonicTitle: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...") + }, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyT + }, + menu: { + id: MenuId.MenubarGoMenu, + group: '3_global_nav', + order: 2 + } + }); + } + + override async run(accessor: ServicesAccessor): Promise { + accessor.get(IQuickInputService).quickAccess.show(ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX); + } +}); + +//#endregion diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts new file mode 100644 index 00000000000..9c9d8c92057 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -0,0 +1,348 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { IViewsService } from 'vs/workbench/common/views'; +import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchShowAsList, searchShowAsTree, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; +import { FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot, Match, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; +import { category, getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; + +//#region Actions +registerAction2(class ClearSearchHistoryCommandAction extends Action2 { + + constructor( + ) { + super({ + id: Constants.ClearSearchHistoryCommandId, + title: { + value: nls.localize('clearSearchHistoryLabel', "Clear Search History"), + original: 'Clear Search History' + }, + category: category, + f1: true + }); + + } + + override async run(accessor: ServicesAccessor): Promise { + clearHistoryCommand(accessor); + } +}); + +registerAction2(class CancelSearchAction extends Action2 { + constructor() { + super({ + id: Constants.CancelSearchActionId, + title: { + value: nls.localize('CancelSearchAction.label', "Cancel Search"), + original: 'Cancel Search' + }, + icon: searchStopIcon, + category, + f1: true, + precondition: SearchStateKey.isEqualTo(SearchUIState.Idle).negate(), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey), + primary: KeyCode.Escape, + }, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 0, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), SearchStateKey.isEqualTo(SearchUIState.SlowSearch)), + }] + }); + } + run(accessor: ServicesAccessor) { + return cancelSearch(accessor); + } +}); + +registerAction2(class RefreshAction extends Action2 { + constructor() { + super({ + id: Constants.RefreshSearchResultsActionId, + title: { + value: nls.localize('RefreshAction.label', "Refresh"), + original: 'Refresh' + }, + icon: searchRefreshIcon, + precondition: Constants.ViewHasSearchPatternKey, + category, + f1: true, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 0, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), SearchStateKey.isEqualTo(SearchUIState.SlowSearch).negate()), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return refreshSearch(accessor); + } +}); + +registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { + constructor() { + super({ + id: Constants.CollapseSearchResultsActionId, + title: { + value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), + original: 'Collapse All' + }, + category, + icon: searchCollapseAllIcon, + f1: true, + precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 3, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ContextKeyExpr.or(Constants.HasSearchResults.negate(), Constants.ViewHasSomeCollapsibleKey)), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return collapseDeepestExpandedLevel(accessor); + } +}); + +registerAction2(class ExpandAllAction extends Action2 { + constructor() { + super({ + id: Constants.ExpandSearchResultsActionId, + title: { + value: nls.localize('ExpandAllAction.label', "Expand All"), + original: 'Expand All' + }, + category, + icon: searchExpandAllIcon, + f1: true, + precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 3, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return expandAll(accessor); + } +}); + +registerAction2(class ClearSearchResultsAction extends Action2 { + constructor() { + super({ + id: Constants.ClearSearchResultsActionId, + title: { + value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), + original: 'Clear Search Results' + }, + category, + icon: searchClearIcon, + f1: true, + precondition: ContextKeyExpr.or(Constants.HasSearchResults, Constants.ViewHasSearchPatternKey, Constants.ViewHasReplacePatternKey, Constants.ViewHasFilePatternKey), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 1, + when: ContextKeyExpr.equals('view', VIEW_ID), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return clearSearchResults(accessor); + } +}); + + +registerAction2(class ViewAsTreeAction extends Action2 { + constructor() { + super({ + id: Constants.ViewAsTreeActionId, + title: { + value: nls.localize('ViewAsTreeAction.label', "View as Tree"), + original: 'View as Tree' + }, + category, + icon: searchShowAsList, + f1: true, + precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.InTreeViewKey.toNegated()), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 2, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey.toNegated()), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + searchView.setTreeView(true); + } + } +}); + +registerAction2(class ViewAsListAction extends Action2 { + constructor() { + super({ + id: Constants.ViewAsListActionId, + title: { + value: nls.localize('ViewAsListAction.label', "View as List"), + original: 'View as List' + }, + category, + icon: searchShowAsTree, + f1: true, + precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.InTreeViewKey), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 2, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + searchView.setTreeView(false); + } + } +}); + +//#endregion + +//#region Helpers +const clearHistoryCommand: ICommandHandler = accessor => { + const searchHistoryService = accessor.get(ISearchHistoryService); + searchHistoryService.clearHistory(); +}; + +function expandAll(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + const viewer = searchView.getControl(); + viewer.expandAll(); + } +} + +function clearSearchResults(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + searchView?.clearSearchResults(); +} + +function cancelSearch(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + searchView?.cancelSearch(); +} + +function refreshSearch(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + searchView?.triggerQueryChange({ preserveFocus: false }); +} + +function collapseDeepestExpandedLevel(accessor: ServicesAccessor) { + + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + const viewer = searchView.getControl(); + + /** + * one level to collapse so collapse everything. If FolderMatch, check if there are visible grandchildren, + * i.e. if Matches are returned by the navigator, and if so, collapse to them, otherwise collapse all levels. + */ + const navigator = viewer.navigate(); + let node = navigator.first(); + let canCollapseFileMatchLevel = false; + let canCollapseFirstLevel = false; + + if (node instanceof FolderMatchWorkspaceRoot) { + while (node = navigator.next()) { + if (node instanceof Match) { + canCollapseFileMatchLevel = true; + break; + } + if (searchView.isTreeLayoutViewVisible && !canCollapseFirstLevel) { + let nodeToTest = node; + + if (node instanceof FolderMatch) { + nodeToTest = node.compressionStartParent ?? node; + } + + const immediateParent = nodeToTest.parent(); + + if (!(immediateParent instanceof FolderMatchWorkspaceRoot || immediateParent instanceof FolderMatchNoRoot || immediateParent instanceof SearchResult)) { + canCollapseFirstLevel = true; + } + } + } + } + + if (canCollapseFileMatchLevel) { + node = navigator.first(); + do { + if (node instanceof FileMatch) { + viewer.collapse(node); + } + } while (node = navigator.next()); + } else if (canCollapseFirstLevel) { + node = navigator.first(); + if (node) { + do { + + let nodeToTest = node; + + if (node instanceof FolderMatch) { + nodeToTest = node.compressionStartParent ?? node; + } + const immediateParent = nodeToTest.parent(); + + if (immediateParent instanceof FolderMatchWorkspaceRoot || immediateParent instanceof FolderMatchNoRoot) { + if (viewer.hasElement(node)) { + viewer.collapse(node, true); + } else { + viewer.collapseAll(); + } + } + } while (node = navigator.next()); + } + } else { + viewer.collapseAll(); + } + + const firstFocusParent = viewer.getFocus()[0]?.parent(); + + if (firstFocusParent && (firstFocusParent instanceof FolderMatch || firstFocusParent instanceof FileMatch) && + viewer.hasElement(firstFocusParent) && viewer.isCollapsed(firstFocusParent)) { + viewer.domFocus(); + viewer.focusFirst(); + viewer.setSelection(viewer.getFocus()); + } + } +} + +//#endregion diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 265e89bc965..d7a75dc5a3a 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -4,35 +4,38 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IAction } from 'vs/base/common/actions'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/path'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { FileKind } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { FileMatch, Match, RenderableMatch, SearchModel, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot } from 'vs/workbench/contrib/search/common/searchModel'; import { isEqual } from 'vs/base/common/resources'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { ISearchActionContext } from 'vs/workbench/contrib/search/browser/searchActionsRemoveReplace'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { FileFocusKey, FolderFocusKey, MatchFocusKey } from 'vs/workbench/contrib/search/common/constants'; interface IFolderMatchTemplate { label: IResourceLabel; badge: CountBadge; - actions: ActionBar; + actions: MenuWorkbenchToolBar; disposables: DisposableStore; disposableActions: DisposableStore; } @@ -41,7 +44,7 @@ interface IFileMatchTemplate { el: HTMLElement; label: IResourceLabel; badge: CountBadge; - actions: ActionBar; + actions: MenuWorkbenchToolBar; disposables: DisposableStore; } @@ -52,7 +55,8 @@ interface IMatchTemplate { replace: HTMLElement; after: HTMLElement; lineNumber: HTMLElement; - actions: ActionBar; + actions: MenuWorkbenchToolBar; + disposables: DisposableStore; } export class SearchDelegate implements IListVirtualDelegate { @@ -82,13 +86,13 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree readonly templateId = FolderMatchRenderer.TEMPLATE_ID; constructor( - private searchModel: SearchModel, private searchView: SearchView, private labels: ResourceLabels, - @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @ILabelService private readonly labelService: ILabelService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); } @@ -109,8 +113,6 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree templateData.label.setLabel(nls.localize('searchFolderMatch.other.label', "Other files")); } - templateData.actions.clear(); - templateData.actions.context = folder; this.renderFolderDetails(folder, templateData); } @@ -123,12 +125,22 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree const badge = new CountBadge(DOM.append(folderMatchElement, DOM.$('.badge'))); disposables.add(attachBadgeStyler(badge, this.themeService)); const actionBarContainer = DOM.append(folderMatchElement, DOM.$('.actionBarContainer')); - const actions = new ActionBar(actionBarContainer, { animated: false }); - disposables.add(actions); const disposableElements = new DisposableStore(); disposables.add(disposableElements); + const contextKeyService = this.contextKeyService.createOverlay([[FolderFocusKey.key, true]]); + const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { + menuOptions: { + shouldForwardArgs: true + }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, + toolbarOptions: { + primaryGroup: g => /^inline/.test(g), + }, + })); + return { label, badge, @@ -151,7 +163,6 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree } else { templateData.label.setLabel(nls.localize('searchFolderMatch.other.label', "Other files")); } - templateData.actions.clear(); this.renderFolderDetails(folderMatch, templateData); } @@ -172,16 +183,7 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree templateData.badge.setCount(count); templateData.badge.setTitleFormat(count > 1 ? nls.localize('searchFileMatches', "{0} files found", count) : nls.localize('searchFileMatch', "{0} file found", count)); - const actions: IAction[] = []; - if (this.searchModel.isReplaceActive() && count > 0) { - const replaceAction = this.instantiationService.createInstance(ReplaceAllInFolderAction, this.searchView.getControl(), folder); - actions.push(replaceAction); - templateData.disposableActions.add(replaceAction); - } - const removeAction = this.instantiationService.createInstance(RemoveAction, this.searchView.getControl(), folder); - actions.push(removeAction); - - templateData.actions.push(actions, { icon: true, label: false }); + templateData.actions.context = { viewer: this.searchView.getControl(), element: folder }; } } @@ -191,13 +193,13 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe readonly templateId = FileMatchRenderer.TEMPLATE_ID; constructor( - private searchModel: SearchModel, private searchView: SearchView, private labels: ResourceLabels, - @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); } @@ -214,15 +216,25 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe const badge = new CountBadge(DOM.append(fileMatchElement, DOM.$('.badge'))); disposables.add(attachBadgeStyler(badge, this.themeService)); const actionBarContainer = DOM.append(fileMatchElement, DOM.$('.actionBarContainer')); - const actions = new ActionBar(actionBarContainer, { animated: false }); - disposables.add(actions); + + const contextKeyService = this.contextKeyService.createOverlay([[FileFocusKey.key, true]]); + const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { + menuOptions: { + shouldForwardArgs: true + }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, + toolbarOptions: { + primaryGroup: g => /^inline/.test(g), + }, + })); return { el: fileMatchElement, label, badge, actions, - disposables + disposables, }; } @@ -236,14 +248,7 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe templateData.badge.setCount(count); templateData.badge.setTitleFormat(count > 1 ? nls.localize('searchMatches', "{0} matches found", count) : nls.localize('searchMatch', "{0} match found", count)); - templateData.actions.clear(); - - const actions: IAction[] = []; - if (this.searchModel.isReplaceActive() && count > 0) { - actions.push(this.instantiationService.createInstance(ReplaceAllAction, this.searchView, fileMatch)); - } - actions.push(this.instantiationService.createInstance(RemoveAction, this.searchView.getControl(), fileMatch)); - templateData.actions.push(actions, { icon: true, label: false }); + templateData.actions.context = { viewer: this.searchView.getControl(), element: fileMatch }; } disposeElement(element: ITreeNode, index: number, templateData: IFileMatchTemplate): void { @@ -262,9 +267,10 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender constructor( private searchModel: SearchModel, private searchView: SearchView, - @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); } @@ -282,7 +288,20 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender const after = DOM.append(parent, DOM.$('span')); const lineNumber = DOM.append(container, DOM.$('span.matchLineNum')); const actionBarContainer = DOM.append(container, DOM.$('span.actionBarContainer')); - const actions = new ActionBar(actionBarContainer, { animated: false }); + + const disposables = new DisposableStore(); + + const contextKeyService = this.contextKeyService.createOverlay([[MatchFocusKey.key, true]]); + const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { + menuOptions: { + shouldForwardArgs: true + }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, + toolbarOptions: { + primaryGroup: g => /^inline/.test(g), + }, + })); return { parent, @@ -291,7 +310,8 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender replace, after, lineNumber, - actions + actions, + disposables, }; } @@ -317,19 +337,12 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.lineNumber.textContent = lineNumberStr + extraLinesStr; templateData.lineNumber.setAttribute('title', this.getMatchTitle(match, showLineNumbers)); - templateData.actions.clear(); - if (this.searchModel.isReplaceActive()) { - templateData.actions.push([this.instantiationService.createInstance(ReplaceAction, this.searchView.getControl(), match, this.searchView), this.instantiationService.createInstance(RemoveAction, this.searchView.getControl(), match)], { icon: true, label: false }); - } else { - templateData.actions.push([this.instantiationService.createInstance(RemoveAction, this.searchView.getControl(), match)], { icon: true, label: false }); - } - } + templateData.actions.context = { viewer: this.searchView.getControl(), element: match }; - disposeElement(element: ITreeNode, index: number, templateData: IMatchTemplate): void { } disposeTemplate(templateData: IMatchTemplate): void { - templateData.actions.dispose(); + templateData.disposables.dispose(); } private getMatchTitle(match: Match, showLineNumbers: boolean): string { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 30454a8e988..48b8b31194e 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -62,7 +62,8 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { NotebookFindContrib } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { appendKeyBindingLabel, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; +import { appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActionsBase'; +import { IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActionsFind'; import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { renderSearchMessage } from 'vs/workbench/contrib/search/browser/searchMessage'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate } from 'vs/workbench/contrib/search/browser/searchResultsView'; @@ -750,8 +751,8 @@ export class SearchView extends ViewPane { this.resultsElement, delegate, [ - this._register(this.instantiationService.createInstance(FolderMatchRenderer, this.viewModel, this, this.treeLabels)), - this._register(this.instantiationService.createInstance(FileMatchRenderer, this.viewModel, this, this.treeLabels)), + this._register(this.instantiationService.createInstance(FolderMatchRenderer, this, this.treeLabels)), + this._register(this.instantiationService.createInstance(FileMatchRenderer, this, this.treeLabels)), this._register(this.instantiationService.createInstance(MatchRenderer, this.viewModel, this)), ], { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index f80bf8d2e57..578c1d2f74a 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -27,7 +27,7 @@ import { ISearchConfigurationProperties } from 'vs/workbench/services/search/com import { attachFindReplaceInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/history/browser/contextScopedHistoryWidget'; -import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 9e5df9fd94e..f0700ce6a99 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -42,6 +42,11 @@ export const ViewAsTreeActionId = 'search.action.viewAsTree'; export const ViewAsListActionId = 'search.action.viewAsList'; export const ToggleQueryDetailsActionId = 'workbench.action.search.toggleQueryDetails'; export const ExcludeFolderFromSearchId = 'search.action.excludeFromSearch'; +export const FocusNextInputActionId = 'search.focus.nextInputBox'; +export const FocusPreviousInputActionId = 'search.focus.previousInputBox'; +export const RestrictSearchToFolderId = 'search.action.restrictSearchToFolder'; +export const FindInFolderId = 'filesExplorer.findInFolder'; +export const FindInWorkspaceId = 'filesExplorer.findInWorkspace'; export const SearchViewVisibleKey = new RawContextKey('searchViewletVisible', true); export const SearchViewFocusedKey = new RawContextKey('searchViewletFocus', false); diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index f8103e8652c..783ee335c4c 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { IFileMatch } from 'vs/workbench/services/search/common/search'; -import { getElementToFocusAfterRemoved, getLastNodeFromSameType } from 'vs/workbench/contrib/search/browser/searchActions'; +import { getElementToFocusAfterRemoved, getLastNodeFromSameType } from 'vs/workbench/contrib/search/browser/searchActionsRemoveReplace'; import { FileMatch, FileMatchOrMatch, FolderMatch, Match, SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; import { IThemeService } from 'vs/platform/theme/common/themeService'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index b395623dc08..5540e023a3d 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -23,7 +23,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/common/views'; -import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import { searchNewEditorIcon, searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index e1d2e65956d..b5a7b7c9980 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -16,7 +16,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { IViewsService } from 'vs/workbench/common/views'; -import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index e3b2e292c52..877d7af2671 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -33,7 +33,7 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; -import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; +import { findInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActionsFind'; import { Direction, ICreateTerminalOptions, IInternalXtermTerminal, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, ITerminalProfileService, TerminalCommandId, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -1610,7 +1610,7 @@ export function registerTerminalActions() { } run(accessor: ServicesAccessor) { const query = accessor.get(ITerminalService).activeInstance?.selection; - FindInFilesCommand(accessor, { query } as IFindInFilesArgs); + findInFilesCommand(accessor, { query } as IFindInFilesArgs); } }); registerAction2(class extends Action2 { From 18cf7911c10216320c685b927632a55846b956b1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 16 Nov 2022 14:48:01 -0800 Subject: [PATCH 19/67] Make copy paste and drop into editor idle contributions (#166510) These are not needed the very instant the editor starts up --- .../editor/contrib/copyPaste/browser/copyPasteContribution.ts | 4 ++-- .../dropIntoEditor/browser/dropIntoEditorContribution.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts index 55c120d060f..ace4c5b0112 100644 --- a/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; import { CopyPasteController } from 'vs/editor/contrib/copyPaste/browser/copyPasteController'; import * as nls from 'vs/nls'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -registerEditorContribution(CopyPasteController.ID, CopyPasteController); +registerEditorContribution(CopyPasteController.ID, CopyPasteController, EditorContributionInstantiation.Idle); Registry.as(Extensions.Configuration).registerConfiguration({ ...editorConfigurationBaseNode, diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index 84632383d27..191aac71112 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -12,7 +12,7 @@ import { relativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -178,5 +178,5 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider { } -registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController); +registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController, EditorContributionInstantiation.Idle); From 0b32e51db31d9ca325cb8deb7b6efed63e6ce264 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 16 Nov 2022 14:50:07 -0800 Subject: [PATCH 20/67] =?UTF-8?q?=F0=9F=92=84=20(#166414)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove extra types - Fix class name in message --- .../contrib/webview/browser/overlayWebview.ts | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index 7bc380d9818..cf1e9a61f19 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -6,23 +6,20 @@ import { Dimension } from 'vs/base/browser/dom'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, IWebview, WebviewContentOptions, IWebviewElement, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions, IOverlayWebview, WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webview'; +import { IOverlayWebview, IWebview, IWebviewElement, IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, WebviewContentOptions, WebviewExtensionDescription, WebviewInitInfo, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; /** * Webview that is absolutely positioned over another element and that can creates and destroys an underlying webview as needed. */ export class OverlayWebview extends Disposable implements IOverlayWebview { - private readonly _onDidWheel = this._register(new Emitter()); - public readonly onDidWheel = this._onDidWheel.event; - private _isFirstLoad = true; private readonly _firstLoadPendingMessages = new Set<{ readonly message: any; readonly transfer?: readonly ArrayBuffer[]; readonly resolve: (value: boolean) => void }>(); private readonly _webview = this._register(new MutableDisposable()); @@ -91,10 +88,9 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { super.dispose(); } - public get container(): HTMLElement { if (this._isDisposed) { - throw new Error(`DynamicWebviewEditorOverlay has been disposed`); + throw new Error(`OverlayWebview has been disposed`); } if (!this._container) { @@ -186,7 +182,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { private _show() { if (this._isDisposed) { - throw new Error('Webview overlay is disposed'); + throw new Error('OverlayWebview is disposed'); } if (!this._webview.value) { @@ -294,28 +290,31 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } private readonly _onDidFocus = this._register(new Emitter()); - public readonly onDidFocus: Event = this._onDidFocus.event; + public readonly onDidFocus = this._onDidFocus.event; private readonly _onDidBlur = this._register(new Emitter()); - public readonly onDidBlur: Event = this._onDidBlur.event; + public readonly onDidBlur = this._onDidBlur.event; private readonly _onDidClickLink = this._register(new Emitter()); - public readonly onDidClickLink: Event = this._onDidClickLink.event; + public readonly onDidClickLink = this._onDidClickLink.event; private readonly _onDidReload = this._register(new Emitter()); public readonly onDidReload = this._onDidReload.event; - private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number }>()); - public readonly onDidScroll: Event<{ scrollYPercentage: number }> = this._onDidScroll.event; + private readonly _onDidScroll = this._register(new Emitter<{ readonly scrollYPercentage: number }>()); + public readonly onDidScroll = this._onDidScroll.event; private readonly _onDidUpdateState = this._register(new Emitter()); - public readonly onDidUpdateState: Event = this._onDidUpdateState.event; + public readonly onDidUpdateState = this._onDidUpdateState.event; private readonly _onMessage = this._register(new Emitter()); public readonly onMessage = this._onMessage.event; private readonly _onMissingCsp = this._register(new Emitter()); - public readonly onMissingCsp: Event = this._onMissingCsp.event; + public readonly onMissingCsp = this._onMissingCsp.event; + + private readonly _onDidWheel = this._register(new Emitter()); + public readonly onDidWheel = this._onDidWheel.event; public async postMessage(message: any, transfer?: readonly ArrayBuffer[]): Promise { if (this._webview.value) { From 91c7eaf9733fbff8076278b5fa1c2945d72032f3 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 16 Nov 2022 15:10:45 -0800 Subject: [PATCH 21/67] add audio cues for reviewing a diff editor (#166413) --- .../editor/browser/widget/diffEditorWidget.ts | 13 +++++--- src/vs/editor/browser/widget/diffReview.ts | 31 +++++++++++++----- .../browser/standaloneCodeEditor.ts | 2 +- .../audioCues/browser/audioCueService.ts | 22 ++++++++++--- .../audioCues/browser/media/break.mp3 | Bin .../browser/media/diffLineDeleted.mp3 | Bin 0 -> 28864 bytes .../browser/media/diffLineInserted.mp3 | Bin 0 -> 28864 bytes .../audioCues/browser/media/error.mp3 | Bin .../audioCues/browser/media/foldedAreas.mp3 | Bin .../audioCues/browser/media/quickFixes.mp3 | Bin .../audioCues/browser/media/taskCompleted.mp3 | Bin .../audioCues/browser/media/taskFailed.mp3 | Bin .../audioCues/browser/media/terminalBell.mp3 | Bin .../audioCues/browser/media/warning.mp3 | Bin .../parts/editor/editor.contribution.ts | 1 - .../browser/audioCueDebuggerContribution.ts | 2 +- .../audioCueLineFeatureContribution.ts | 2 +- .../browser/audioCues.contribution.ts | 14 ++++++-- .../contrib/audioCues/browser/commands.ts | 2 +- .../browser/inlayHintsAccessibilty.ts | 2 +- .../tasks/browser/taskTerminalStatus.ts | 2 +- .../tasks/electron-sandbox/taskService.ts | 2 +- .../test/browser/taskTerminalStatus.test.ts | 2 +- .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/xterm/quickFixAddon.ts | 2 +- 25 files changed, 70 insertions(+), 31 deletions(-) rename src/vs/{workbench/contrib => platform}/audioCues/browser/audioCueService.ts (90%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/break.mp3 (100%) create mode 100644 src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 create mode 100644 src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/error.mp3 (100%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/foldedAreas.mp3 (100%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/quickFixes.mp3 (100%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/taskCompleted.mp3 (100%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/taskFailed.mp3 (100%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/terminalBell.mp3 (100%) rename src/vs/{workbench/contrib => platform}/audioCues/browser/media/warning.mp3 (100%) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 216e31ab49e..0cf378ee559 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1878,6 +1878,11 @@ function createDecoration(startLineNumber: number, startColumn: number, endLineN }; } +const enum DiffEditorLineClasses { + Insert = 'line-insert', + Delete = 'line-delete' +} + const DECORATIONS = { arrowRevertChange: ModelDecorationOptions.register({ @@ -1907,13 +1912,13 @@ const DECORATIONS = { lineInsert: ModelDecorationOptions.register({ description: 'diff-editor-line-insert', - className: 'line-insert', + className: DiffEditorLineClasses.Insert, marginClassName: 'gutter-insert', isWholeLine: true }), lineInsertWithSign: ModelDecorationOptions.register({ description: 'diff-editor-line-insert-with-sign', - className: 'line-insert', + className: DiffEditorLineClasses.Insert, linesDecorationsClassName: 'insert-sign ' + ThemeIcon.asClassName(diffInsertIcon), marginClassName: 'gutter-insert', isWholeLine: true @@ -1921,13 +1926,13 @@ const DECORATIONS = { lineDelete: ModelDecorationOptions.register({ description: 'diff-editor-line-delete', - className: 'line-delete', + className: DiffEditorLineClasses.Delete, marginClassName: 'gutter-delete', isWholeLine: true }), lineDeleteWithSign: ModelDecorationOptions.register({ description: 'diff-editor-line-delete-with-sign', - className: 'line-delete', + className: DiffEditorLineClasses.Delete, linesDecorationsClassName: 'delete-sign ' + ThemeIcon.asClassName(diffRemoveIcon), marginClassName: 'gutter-delete', isWholeLine: true diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index a615c329e05..12889bed475 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -33,6 +33,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ILanguageIdCodec } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; const DIFF_LINES_PADDING = 3; @@ -66,6 +67,11 @@ class DiffEntry { } } +const enum DiffEditorLineClasses { + Insert = 'line-insert', + Delete = 'line-delete' +} + class Diff { readonly entries: DiffEntry[]; @@ -95,7 +101,8 @@ export class DiffReview extends Disposable { constructor( diffEditor: DiffEditorWidget, - @ILanguageService private readonly _languageService: ILanguageService + @ILanguageService private readonly _languageService: ILanguageService, + @IAudioCueService private readonly _audioCueService: IAudioCueService ) { super(); this._diffEditor = diffEditor; @@ -149,7 +156,7 @@ export class DiffReview extends Disposable { || e.equals(KeyMod.Alt | KeyCode.DownArrow) ) { e.preventDefault(); - this._goToRow(this._getNextRow()); + this._goToRow(this._getNextRow(), 'next'); } if ( @@ -158,7 +165,7 @@ export class DiffReview extends Disposable { || e.equals(KeyMod.Alt | KeyCode.UpArrow) ) { e.preventDefault(); - this._goToRow(this._getPrevRow()); + this._goToRow(this._getPrevRow(), 'previous'); } if ( @@ -215,7 +222,7 @@ export class DiffReview extends Disposable { this._isVisible = true; this._diffEditor.doLayout(); this._render(); - this._goToRow(this._getNextRow()); + this._goToRow(this._getPrevRow(), 'previous'); } public next(): void { @@ -250,7 +257,7 @@ export class DiffReview extends Disposable { this._isVisible = true; this._diffEditor.doLayout(); this._render(); - this._goToRow(this._getNextRow()); + this._goToRow(this._getNextRow(), 'next'); } private accept(): void { @@ -312,12 +319,18 @@ export class DiffReview extends Disposable { return null; } - private _goToRow(row: HTMLElement): void { - const prev = this._getCurrentFocusedRow(); + private _goToRow(row: HTMLElement, type?: 'next' | 'previous'): void { + const current = this._getCurrentFocusedRow(); row.tabIndex = 0; row.focus(); - if (prev && prev !== row) { - prev.tabIndex = -1; + if (current && current !== row) { + current.tabIndex = -1; + } + const element = !type ? current : type === 'next' ? current?.nextElementSibling : current?.previousElementSibling; + if (element?.classList.contains(DiffEditorLineClasses.Insert)) { + this._audioCueService.playAudioCue(AudioCue.diffLineInserted, true); + } else if (element?.classList.contains(DiffEditorLineClasses.Delete)) { + this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, true); } this.scrollbar.scanDomNode(); } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 8e9a237c5b3..9f15f355956 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -495,7 +495,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorProgressService editorProgressService: IEditorProgressService, - @IClipboardService clipboardService: IClipboardService, + @IClipboardService clipboardService: IClipboardService ) { const options = { ..._options }; updateConfigurationService(configurationService, options, true); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts similarity index 90% rename from src/vs/workbench/contrib/audioCues/browser/audioCueService.ts rename to src/vs/platform/audioCues/browser/audioCueService.ts index 6aeb6d66f25..a7ca2398964 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -17,7 +17,7 @@ export const IAudioCueService = createDecorator('audioCue'); export interface IAudioCueService { readonly _serviceBrand: undefined; - playAudioCue(cue: AudioCue): Promise; + playAudioCue(cue: AudioCue, allowManyInParallel?: boolean): Promise; playAudioCues(cues: AudioCue[]): Promise; isEnabled(cue: AudioCue): IObservable; @@ -39,9 +39,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { super(); } - public async playAudioCue(cue: AudioCue): Promise { + public async playAudioCue(cue: AudioCue, allowManyInParallel = false): Promise { if (this.isEnabled(cue).get()) { - await this.playSound(cue.sound); + await this.playSound(cue.sound, allowManyInParallel); } } @@ -70,7 +70,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.playingSounds.add(sound); const url = FileAccess.asBrowserUri( - `vs/workbench/contrib/audioCues/browser/media/${sound.fileName}` + `vs/platform/audioCues/common/media/${sound.fileName}` ).toString(); const audio = new Audio(url); audio.volume = this.getVolumeInPercent() / 100; @@ -164,6 +164,8 @@ export class Sound { public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); + public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); + public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); private constructor(public readonly fileName: string) { } } @@ -247,6 +249,18 @@ export class AudioCue { settingsKey: 'audioCues.terminalBell' }); + public static readonly diffLineInserted = AudioCue.register({ + name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), + sound: Sound.diffLineInserted, + settingsKey: 'audioCues.diffLineInserted' + }); + + public static readonly diffLineDeleted = AudioCue.register({ + name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), + sound: Sound.diffLineDeleted, + settingsKey: 'audioCues.diffLineDeleted' + }); + private constructor( public readonly sound: Sound, public readonly name: string, diff --git a/src/vs/workbench/contrib/audioCues/browser/media/break.mp3 b/src/vs/platform/audioCues/browser/media/break.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/break.mp3 rename to src/vs/platform/audioCues/browser/media/break.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 b/src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fc7ec846611b7eead98cd54ea4b18aacf6e5c25c GIT binary patch literal 28864 zcmeI4bx<8oyXOyZaCZ&CT@LQS-8E=%mmt9*9NY=+?vUW_?(XjHBv=B0v&rxG-n&&> zw{F$e-r9ZlzUeMF7AGVD4aPt>NmRq^zb0AOrnQY!Yn$FvPk4HY7y3{$Ys!qX@(Y`uoVg z85}%3e;?uC5f%MM5fAUb8-TxB3;4Iz)D>04{?Xd62LJvSJKH}DK5_PcocEVIATcgc z$$vlam%%A1{%_6y+u8qK&3|tE*5pt6qre{p{wVNAfjarfW(pWi?;Qf3&qgo|V^U=mq6t>Egv` zuIWm;i{7`rT(Mf~b+KB->^^ko<&+d(ZC*aUwWf~DzGgXyjEr1?_J4}p8l#NNez-R6 z$ak#bj?laQgxF5&MJ<2hLqXYjdu=YTnfTE5DAU%fmBW*-7}?Q_M3~$BRa7o0BYuj2 z&TJJqG8~|?nA$gjpKN)nZ-WcuiHqJSLX%-8oS_?ABn&C9dvHPe^YQ{3;sx>I=@SnDQ5P zsot7in)BzIar=o7PCBIVhM{_onQbkGnt(TkeF2x8>MvLN`D%|&{=^5Du88IijEg@g z#K*lD50m7HI|?t;@&=5)=(2GQSsH4-ktBt-leIeGAyiV(?rV8RbvhxA4&2PwsOK_S zC^P8`+ipsRJ25puK7OM;@wm1m$IEo_HMf#N+&;23@z*!Z=BlHG9=gUy^u^Itvl~dQ!vp;8Q ze`c{4R_(3#UiCb^!D^2rx_)Q;?#CfHJAYwH#kc1tl3KOqkSaaT!onXPD&lb@C5SH{ z43#_6slv>i_oT}4}(42+eH|T3BeLEExBDgX4=N=r4bhcv&vq~VT zAwjyo>m|)$|QTE$Pg*i zGaL*602d3w6oG|-TjnHV!-Zb#?xn*=2EbK`3=^Wf!j(Y;av>q9fJA`cZZLw^#0l6D z1qLS93ti)TQhWV=?R~}ZwzGMON{IP1m@8XxIU;yy28wj4E z8jSo3atQ+Sw zCo4;zMWvB_L~>oBq+Xl}Ol2B_^3|R$BOFad1dJ5Ll}ksHCUXjjqC@99F?eDLzf7z) zxCongov&Ex7ZG=8oF=$URWX$(Q9jH2ojZ4#q(B};khXhtVSjeFLdEtdp{@6RhYjjU zMqclJmJgddBIq7OL`CR6hSTV#fg$Jt0q48xpsvq2Vf^x=Z5ZgZQ|a=NfOuiOc~A(5 zapQt&v|c6zo;ir$*CKIKlt!6HE3~>S%1b#t>LXw&wolb&cOdkRtLKNhO@r^>j+wTw z&VS$LEM)-kS~Z&ykuZ5d?tJsZ82>;(6YBvWB>~i2#t9(N13>pk+92n}@Ql zsPSIop$u?NAwUgK!&wND7@3g=idfO;?#VPGesA-^Mg#PciGrZIATOA_{G8YMXl#{3 z=a_;wT0Ro|X0{j-J3vxL4G#xFak(%Ov z{mAya|B(g&aYzPWHoR2)`*@AKKk=fI(8Cd;_`6QCJgJ+B9F<`t0{K zKUSbF{C=t7nG156MOblsyn%XVYp6{?3r2zp+(b7bAS5sK56WgPCBX0xhe~PwNJhZ? zn;!!(@FG?s#6SelhJ_G2HVr`6*F<^siy!z%6POM#0D$Zdj)5*E=pgs;4{gUYB8b~| z@ec+=r}Dyp#sEh7Pzo>r38;W^cn~xIPl*X35X}QV4@OEoRs;!8%s(6j#vjnlJx7O< zn+<2>Us)0>)(W3MGE0)SSozfIE{d}OQ(^16viO!m+Y`Gt#!`@yq@A*Xd z9&$H*zuMaIq*1kX_HegSb!Jep@M|b}oHlTC6{@OyunrV)ONGT0O)~}%&IE*~d9!9H z5s(M(yh~jTC=2T*CE$Wa3`|u_WrOwy=c1)T0aU?Y#5h3ONAj8oW&IWQ4p?R=J!T&L zIkvhlKUxR0;CwZF+w@xZy-Nms7tYV*_fZE>TCUbkT>KPcR&3JFe|*3AMBHUa)Ls{# z0Ml&`Ql_TFFvVhj=oBwnyW-WtDxfYr0>NX~QHryI)S)?m7&V|-Kpn6f5%daL9caXe z1PBL%p$JgGc{i4@<#|qN>K*p0A9I!`Mr{RJ6j9~v_O#qd9S+E4K74J67EB%)FxOo& zke0_SnW0&jDGqr#@WwqqscM2i?w@m>ML3A&WD&e*{hBpit>>QBqG==VTu_!u8qT%>)2%bgKsyA4_A#oy&h3; zY$;3_26!_VrJ_IgfZsUBXZUVN%_8Ath#4S4R4)1e+qyMdn`D<){_s<4>+RC1`{HkY zM8H7!*ZqM%O;mivk8}O(XMU<|7Pb_NSD$mSxfU zCS^!WCZhvdY9&$jNoS>rY!hR?4dXU|fwbm%k$04Iy!<#nk>6=Mr*56(fT;WoI}+A?q` zvEs$oHm1%}qY)<8A>ZgCjP_GTBqJsEi*A&$#M4?F8|{JQ_8FU|^nO{9Ls2nHdEsezn=V7MMDJUQfomO#$EQlMZlOc0qE9tf@b zJqQ-81_khk_h04Z!nYYGq%nQJzLx&%79aeazb^%#nf7AVhOUU`gcNm*8@_M(mUBiN zEx3I%yK{t!6_njHCC)iOTD-!TdSq4Q%P!Q(cv(=ecJ(L>IoZn9=;lCN?rFr3SY~Qc zn2aJw>?TJn?ahL3?;b;t>F$CiF#?Jjp~B(89EVx+cW60V^j)ndmG&)ODRkA6N*Mjs z{{Ub%$@^ixv#WR)Sb_cE7)H+;&nI7XV?qopOG)@u{2xkROcVI+?gAHur#eLDV+)Xou59_n@b^qbl`}Om*%??71p>t)J0Mcri+|zAJ!@)awMl2Tg zyhwB_voG9f$h0d8RLJF3zg;S7Q5xmE zEsi;eNKw(~d4rtW=YmK#$>S6|VgS?MOZRJ-OMs#~lfKo(LrcYhJ4k{;VQ%H6k7Z&C zPs}vi%toL?k|~R)^8muAn$d+y9S7?@m#pX8)#xONM80OkKvk>nH??(nroUWTn$d0R`ty3NO8hg`uv)zP03 zT&^DBi$Ys+ddJca`xs*#ukV$G)ft~@$SftSr6@!3S6<9Hx#48V^G$onXxrY{SXl%V zV`wRG=SLg&hJ?W+Osu8l=hxTQ0H;tn+fVQp!5GKiW#MTfvUG-Y=VQ>@3yMkRoBg;4 zCKEjCay9BgV8(qq$}yu5@PxIO2+V8=1&u|{=xujT@MMFZ?kFXzdgT$x;egk#i*ZtG z;C}Pt34k%h7Kd4r?e>K_7V);ALDiN=Lq7?HdI~j$x)e-hXtlJ!nN}!f#;7P}Zl;yO zu|kA{r(=LO+ewWv6A2zjk?Be0q|D*kZc5`QoDAG+NaQjbeo7H?cgm2cl@4cQIZxk#!=NCMg z7CE11G{R&tI%dIreHlB1mo zV*@J=Al-AH8ntj-!_*=w+ijqFyC|7JSWl;jA?~%@yMB{8ysjDKr9j=04Gvmgvo-BMW&Zgjfy7yV&0&L;?aw>rdOWGic`rP^=WfW z;DN$-Gd}*Kjy;n&140Wgl9t~@&MbDp^rX&X{8!C2(yf_jl%(f5w=A`@#J!d{FUvfy z;?Ckt>2uGg*@(cGCDbc`)#mNbW1SloPRW9;^dk`vkZ2CSkBFZucuoj#4Z0zNVI#K# z*&*&h=b{Lu8OyLRK>LtI51HTPC#a19h`Ui0Fcq^1hB?~Ri8fFiz54nDwuqI@hm+N) z-B0l6*UQ|cZw2sp0*(|7W>vvPeH9-iUGC4CCwq>J{q8RP@hjb~6fH6!HJJ5R^pI#f zNNbw#Ap{Z!flPR?zKmlcF!e?Y#8`78T&Lcl$j*6ev$aISgnJB6+r>0fkyZDN%Y}!e zVwlldnD9g6TgoAf#{z1gW2&#=Upd_d4GACD{2bza6O1x$jph3Eo^PdoOeLxheV4IS ze=M)->-viytBKCmPlKcDozSas_nogzqE(m_a`grwRTaSU?2YMVXhp`5V7 zhGyik>?nibm+oZf^Bz&M@vYCCc0G59&4UF%oS|d5{@z8X;~q_@b!pyjG{SbMD=@mv zGMIrt5=8V_ShwzJEOx>j&)n2>@9Q(J(I-KQfPy|H#DRMrD~o;tE(&{z#5nUL-luQD z1F2_(#(^;BeWqH(9Kv?ZP}brkc?1NyMMslH)O@&&3Q5PEk&T-1Dp!m9U^MtTLUBS~ zPBwKUurWxCl7qmA2ZRA-Y=jv)N0=h@W#iZ$Ly;u^+??6{P)At}Gif^GbNQ={>2UHS ztNE+R>~(>uz48i;P*g@sCH>p+2gTYnwAa*Q+R1nym5EMFXG`~2#6Bs+shUlF5}vwg z^lVC`5V)sZ!5FS(WUX;yUVUc}c1UGAc*n+j+4bRySWFHDZQ3x3s82;v*&GmsV-%@? zNv@+HZbYH<#@I0w;DxNN+iehL(WCO~+yYgHJ}F35I9+kheQ9J0q2; zfie&S&>E9mcQrOzw#42vwfhnIiVVF_V~4tVW+*{CM;q7PdBD21s;$`5E|OJ>&dye? zx9Bt|G&tZxkB>|=G?=94m?8=R_%jU0zk8So!7hZvn~rIO0~!QVN2w|MI(sp+A2>Zy zmiR%+@l%G=Z+;2@z=)4MFayNSzB}3?ehO4ro-@$sDE6b8*||M;(N5Z#LG+kPNrp;E zr~11T9+XH@Pjvymp3qds9#*X-8p%41p}y$6KDW3&xJh8&QU90_C-?@_!pFeSTCP(k z?W?YHa#M=nMLiV>25hs&ndFaBn4*0|U)1){jsW#~Bf)?-#$HgQ)~&SACa^$#BVITn zqYHqb5eQt33h?PB1fUQA2tWY1b6~e189Y@we_)1^ue^c%`eXE`WXa2k(&97WiALqg zv4+!hWk$P?amkRBB-RtscZrcLK?R!EwDVmvf}Jm*604(EQ;n_WDQH*bdG*RJ1-7xm zXueHlki;wR%FCA>j^lZnCztUj$dg-VH!!JKC^z(LLvG&BL2`tVSP>MmXlPD!5qiX+ z^9Jn|Uymipj|^K~d0XzTby;&oC6xw~dFq~rYpG^u82nlL-Ct)CVS-1t(J556^_?XH zAFdtfaxZ?29>Dh(*K`F41pB;=U7x*tAX@-=^G@7U#61WXLs|wV{8$B=hZZ&*Hb0IN z!esWL^nOA#Z65N$7T+t;<0h9Wek#C&F-~frgVT~x@cs(OdMoW}h z;*%?qto{9IEvAx8H_a^BG0k-s9AXq44p=-GDY};IXve<&A%sEt#kkX=w&lX*qLKru zLqIxHGPdI2QxNxX<+j4A-WWUERK}eodFmCp@ zWlu|1o;K>!(k5Hyb*H%lNrC2UaF_Rk{fnPrKBF+HV>8j(SFgCEf~7VOZ(&G^_D$E} z!}tI9LAu;&fL?{xTUnv9U@Bv#b@cQO)z9H8;$_vvu7P>Fh^d$5hV4?YNFC~836o*w zyL_u!{ajO@U0Tu&>HFB=KE}S}LrbOv2k{3v1uM?T*OplvgVc~*kT~4PF{AaukO<|S`gS>_Sbw7YQF9_WXG&%keP~jvPgepbIkR>+ zqElYy3c15^t?gcDh$hrBa%N`MY1YP;Z#l{>-hH@B z4P9oRi9D`Km!oWiko-44r2t@as0d5}v2zkL0)~gFgPIK=0PR=*g8?G*pNVb}XH!z? zb$6z=3g-AAV%o8r#e}k;%coY{+BGy`!&e%!+Z zB~E`KXGq~t;!De@7<)2-K0d{?o&7E=ZOM0XjpOrf z9J`)wWNA8aUba53gL4jpjDC02w-H-RZP&>+ty1!7#BEjg@$Dz>6yJCznwG!$(PQ|< zmSjV`;>#Z13(yE1;`m-jujwBO*=Os@2xWhFnwB>NGt0y+U$Ce+iBhhQHVCwBN*j%P z&d0%tKi_EAMDQEHQX5jR^o4tAr~6uGEbnxXS$HDH$Img9CC1~~xG&cuiRaDL+QpM^ zSDQw{0#_$q8y2zW&|ll>C((wz?S&HIy<{kLS*Z{%Y1QfA`;jp|P1q10M>}3MJ=uJB z_ELu2j7^P{`r+P%$6BHr2^EiNPQ3P~jZ5=~IQp(@8j85j*}atfKZRw`T1fXHv22dD z#T984>?MZh3H$B)Jql9S{4;EpU}GAXED zO^PpBh@)zJxoY#iO{-fUO+42N-?SU@A<^q>IY;->=E!lGiit5afnRt6B87$=QIJy! zB*+JMnC3KEt=d^g6UJeTWB=9v0DuUxB7uQK-=Z;OFdR)Cl<8rCXh7TWGd42+?&ucY zalT}WmRI+R$o#guk3+0@Y~l~8atXy%bkMG4Da7lXtW8cw-_~j zqzQoZlOG)aGl$o+wx!At^cni7?Ud|Wd?gmz{A zBGRK3)kJ`Vc1Am6mA=Ad_}igZ-%wU6)vA(e*}l7UK+1=8A7ja$%+=%uq=!|T7RU#@ z6K)VHx_iJ=j7v7M5mNR+YK#uUnL)$QMt0NBeK!bvhgHx$SMX9(6;a3BcxDGz#*8n1 z^Yc+<_&^6tZ`1W!hJYUWPrfgN?m7EC1#v4*+ESBob&!?CcI>MdoNKr}jn^fSPLG%O^nw z)f3%f7jASpWMP>ToBHVhEMth8&5rLub-;m{T}U`8#h;Aih9DsqFuYVGt2im9lEGdI z5MO{u5@50|7u@bJswNHp(4FE}#>h1AoO8*aV9n^O04;p;W>om3eX#C$X%C%(k!r|x zgK7ZsLU4Mm_$rrnhPPNZ6(r3}TP$UUs*JJAL?TN_$@SKPnx;OJxieh$F_~cH*vy)e z`11Vo0is@SzcUy6+cu7^&zFZEFY_ZQ_nlcERy#o-uTYo@_qFP47$R$Y@KJhwkH?Tu zv05@cN^DVmSnDaZ8ZG)fM0?;F1b3z1jXsVsuTAM-Gs|J9}{L?Yh=XFnDtDOa47_}Fo%Qh+doZj2c zu!av4c1L}MZkeWT!91ilGvT1V86|Wb+*|xohJ+tCKN@ABJg6l!KONTv|5TaU9%1vt z=o=%sn`l4$(}%@Zu)H-Klp6yh_wTKfj@pq~T_(4QIH>8x;go25k4^49QH#!K%4!GRWnJ7vX0Hk|svqx5r`MVT0R z>NOIriEhDCLvfr?5g)yJqq_bTUQmpvU2GZR)Ao60ZNsIa&UIx(s`&Jc4!)0X#L}Ah zO6RwOJGAnzv8M?9J)>Vab-!uUc$}Aq9kF&uh*ljD{c`JS2~fGiudl@E?T^Znw#V0@1J|jF-LAEk02h9G~{0CeN}{ zyg0}N97xf>J+J)6n?QT=mRq2Wyt@bwx>YM?m;;6@s0vBQT^#Rxf$&m1H%zyDG(+S= znJVLI(3Sqoma@cr0M&y~#OrA z`0|`$ef}KU;ASv`a3r}gy$B|qN&b={zF1KDU;HEkVETBw11X7iBV+&O$L{tgO5s?< zn_vCUFMhN$MCxh;j`xL_u10leB?dW;hAM3aRa0pTdP1xLTKYVJJ zQn(7L%|~)JrfhCTkUyL`&W%Jzo(}0xK&k*&F>QxxrGn@){{h~wZtpO->iep z%E4?c_fb(cdn0<3GM>hzQ1bnIB#T4uxK2S-1X>T(vq;hf+VhxuQS#z+pMo@0a$0IY0FW zjpyp~Snz9bDf!vlK&xDL2yll43q3fQKI@C|%DXTzyo`uFLcnY=J)&b_G;x-%tS#H* zph;ZDn@t&)LZ@x^ttN9ZeMQVRX{zD+M=pEkL&AHX=rwP3rDx$6zOVR_eP0TU5G4+Ceg+}yt3cUawLeq|DY$}Gcce#3%5T3O9!SL>boWS{lNr-%3Q?z``P z!b{&@y~yMF);?5}gpd4M4%y3|t&DYSG9yCm_Qj!ui8SFk5J_`bQ5C6vAd7sBniN~! zZg6IH>w1x0=VS23RC(i@L7hd#DrPAQJtqt<8q^gY1%u!<=PM7j;WxA58VrI4iWET; zGzUTAICu{Cx#_EF{x%hw)nyAg@#3dO@tpAwm|5DzT742o@W|fTu4qp9Gd{76!qvbJ7MErX+N=by+DPL^j5%f=#4Swtq* zuWwvlq~(R7FKV6}FVkOupz1CpEX=MU$8z#>>}n%&(u{NH`veCoVGb8Y(KkFqGu9qz z*qEbgo#4e|9v;Eo!i!tg??+GRH#^)V)QO`0pv13Ujl;f)dVbJr{u9g@9p}!IIX{bL zY|qP+z=N@$VS#THR$s?Ri&pP*LG5l8tBrid$U#)3(frL%CKwnX)E?InhQosN}(8|M!cy$LV3i7;Cvk84!?5*_JaK+q!!KL9I)6m!ZS;O++J zQaXrR&xW96HZK~>eXb;>-_ItM%MQ{Jp<*Bez(h7{>!TRW?m=}oS6q+^PJbo!(b8co z8rY}^jwx{ti_{5HWLkJ#S)f_ADP~g=X56q>L!zoup<7>xU1&j(=q$t-Q1@OiaYG|QE1lTg zneqdzEOn%%{-o%yDZE=!p0-VWzI{Vh@xZrN^S4c_Bt@0d6S-9fi&YILO{<1^eVU)u z^^+c=vp>U#Z5-Col?056{}3CoWEK)Mv^XE%>g(oLR#&et{LN1a04UDc4ueRv9jzEz zZA|H)$~k_G0>s?kfW4~O3&Bel`LI>&56F^Vqz>}5NF)2^>#33<NaMzILugGAlFDX@yS_kXS#lrh2{;%Kwhz}a72XU_H(FI)>p5> z+BMfx+w)DWF1I^c@5#bo8Go*0>&7_if|I6n71yP$gWJ!~Ze7HF#x7ZU1}>V!T|d0H zAmv|vUSTX~+OA9LeGFm3G5Junll!GNm*OkF@elgQnbA^@!LK*SopS>A{8vaRdCCFU zoChDMYl7G{B5r44Eyjx64mM30<6D}GC1A2V`_oZJt6ND<*r5B3$g9>*V>ER15Qo-& zQc>^KzWBJ~su}n?Jha@{54?W%uDGbXQMu|WUmD}My|mu)`|$n>vNpT-oi_a1wuKHK z#@T_Tu80B0A1tCJqDTQnl?YNqWg5evdljGxHLVg72pR?1iXl?C&k@4C!vci$8{0neSINyvN6lIf+zU^Zw>K zw`A4E(iv8x(i3PdAKh#Dwn+cV>#QDQIvJDB>{W)7?g# zo(MF?(<=p0j3lbcvTxZQ$tcZ|&Z#}A8&8^g%j|zR`R=|XS9(Rdxm}gdPHmLSScsVB z*XiZK!S2~!AchOfZW9>}SHpAc2;zTp1rhn*+tZ^I5BoA&d zX@ri67@pQtjhk)mG+3%knikZq&KH}c{2Y1{n*JR{b;LSzV$8TDsPd4meX+EvfrF{F z-8<_dYU(wOP4bp}Wxq*~$&Nm&>lyC29liF$RI$IH37%(3ky@wV+nSptR#k1C#mk@D zTXp(B^?PsJJzXx$rRKL*m*}eqhc@zF`Cq}s+MNAByH%=V2*ACGLQF3we~l@)k^ezk zn_4xG!7A*_-1s+KqDraeG_gpj5%k$BY_k=H`uZQd`$?C2S@1k%1FtkL5{Q=G?i^V= zT(B-Z%nM9tDzJ4?k=V<6$5I{|>^}HZZgMC81m;g0un;XQjn>#^eEPT+A>Z?8+B&~3 z@1ogp64Cj__HfRj7MHKWDmia9ED#psgf$Vx;!^&yjy4uLV0wo!cCdJv9RD{z`2b)d zufQ*Ub`xdb9gRcu)l?QQQL4uxWU{k*64RU%=wumZjkV8|K+e7m?0V6_)(=OY^#d4% zM4wm)?>{}l6AT=z8F(aQ&GPKbeSL9oeH&CFN-8;DgRw)XiwtYSJzHKg8l9P4*Sx>$ z-gxCzsimiLGaoL|RUt>?$ZWk}6OVh7UHCmH-{R@n-eSSsyLVkkSlWSF7PYrmSk|U7 z;cmtCEk~=yG9lDs5tL?3utaZCG#MNyZJ*q%kV3i`7*mhw;W2xd+)ii;U9+0@*30jF=$0Hpri*q z`up-Jw`YeKyBN|YiEryqq+*pxH&v_rn;V@k3g!D?B(uq6cKvd9>Vh%CaqickFCuc) z@o(QS5Ldn{XH}WEPCZmcTv_CcXPYdf8~ZF<{A~?wpcQMXqKL2Z1DnJOCTc8+w0rCP zeE|QF5beC`-g9wFr+LTf3(ecl_jOrU?^~Cs7Os{~9~Lf;Tp;b68_8N%S8jC|mJM4+ zXOPUrE@NfFUI-p8^>$Kl9B)$Zg8#|!aaNJ|smuJ8Q=H4Bw$90G&Et`UU@d!t#? zsei&Zp6j%gRkf73#<*rLHB24qJm@u6`L$(TT0Z^qW6ohcd5ju!l@0+P>Rn~f-fw;) z06-Y3wm>F!wXgwNBl8dimGk0X{ES3UrDpXcqdLV^m3p{QA&7a~N8u4LvqjhL%+am# zB#oIUYb;Ts)hCBQdDf{|%cGmtd`dQa%Un)ZHS6KIE*MYSi*S`6z?#nVk?&wzboG$3 zqJ56v+Tx_wz8%$fe`*r6{`#u^;we&oaP!|xE}X~EC-{e(^rS$KyEmI37dE$ZOxf^4f;dS{>s5 z8X;NgO}SJ-zq=Y6jbiyRxvAwjx+cbVluVn-?IScZy>{DbIhxp!z7kxZ$nti1PbhlW zZ+;>HKv9}Dm|y%j?+t;?Y2+|T{^BQm4P_H5nP6KEEuo6K2J2hek2Njc>Ypd!0(j5 zJmMPCf0**MSun25RlRt=d_wmL04+6b*jCluJuP)@R5f*qb7u$akuQuHoENo;OM=ha zm4aj(`*&3PDq%=<1sY2Kv{9Au+HAB1B7gP|x2Mhg{S$ppy&&|o~X z@7K-O34w(Ku9u|xGRf6@QsgJLlFNmo#pds2KNRp5Rk-Cp7+O3XFE2KD*{w$l;8h5E z5Pn1c>>^?`1*4wGrR@PLFE%%G10OA}`N1x$b&ZWAHcmwsSV|3q1NJOCQaa}L7s-FF z7^dqrLqQ<19%H4Oudxf3gTA>+R8Qe(h}^+mPg~@gu=Dzo#Oh`z?x|5(#WGuJ-9JeaHCY{s zrUbyx0dfmr)Ovz0cCNaMtX8@rWO?w&m=cs}IO~{sVK}+vRJ8*GF>HxGYV*sCHupl} z4Y@VUUG#B_Dk9U|%e;0*%R;=3+-(04LJ%lD(&}`e1>HYb#DT;=Ox4jb z3NyFePNo50yQj4WWMYTOWog#zyRSHg$*Dpvu%*W@iz;F*(5KA&Nj8*p+tItDO;(z( z`%veq<-74YW5?dQD=p~P8$QWPiw&1r-W^A}3;Yi+59`-K(Nw(Ri49xeMFl>BkTXEf z*Y57^?EzP1bY;HQO2x|qj0>Xg?6+WlB!@+F8(b`Zg^R|Aw!}HYER%8$)LRjbQ)*_#uPyP?2uACOD0zVNz93k6=b%{%3=V5PjdAoLl-?PNiDF{w?2? zflfWx82X5udC4tj0nsrnS}RaNus}&jiXV}pcqC-RP_B1(<{fS7za)Fz1zj|Xk$&dsbkkMZ0E7CFKp8=KUhIj(WO8lHB(G~$Geo*$9_`RkVx(lyM4Uiv4F;4Lryc^yN&`-p5imQjM@8O>9wqi;YfYYUoY}GH`Zz=O?4)F=J#hWWUx6eDoAlHh5Ua zpRl>uG%>iiiyaa~kgWNoy7zEyib&6fGntE+Z6Y!wpC+%rr1+fLvNK`gFnuB%CnmDU zE!cB&JsQh?POck#TjG$pZm(yXT64v(=ss-QMpe=IctrYSqww72de!NBzkn{#Wxu!O z`p{{`xgDgqGU__*(`xgPnh6hAJ(cK_g$2yh?8h67aI?E8bq-+&mA24`DK@z0`lmLAOukYG@XU6S_}k9$V)&#G1KlM89Pm;5b!rIl z!K8LBcy$*hhgAZ^YTWF*b%16oCGta+jVKWvPZX_5#|o<2nyjyPpUMOmP0~e!+B2Cm z1By49;w*@s!>%#>L%BP}RBzepdnb+NgWPyNvbcU4Jg16Z?~;^9-ZT#`Ivv(O>bzYo zf1c>rP>6gQ_IuoXS!%jCd~?M2eB^oh-r(Y*W5V`n9a7%)(DgLl0AcQW6?yWddQnfn zHL-SO#1>9U3NUlv1MH3^5bl15o~+I7cq8m^w9DeE9B z7Xdr+cmD&^O)vQC`HvBp%H8qT_@%O)e*TLeInwwI|CBWP9Z^wHeWZRI;@);Toarh! zBqSt1QKub|+}zyCSJQ^ym6157tG3_5K?dBU^~kGh1%AB~PU%ArfxL%Y7j!|s5ktbe zAYTGPa#6T238SsK@vu}@vgjP+6znMkwagyVl8jsLTucK-zka<;?Zi=W-e5nWROivU zkcnk!A}$@R80<&7nIA?9#OP@r=;#=?c0DB0x1tKo?2n0yb`Zn1WJ^)@Ht6_y0il&2 zFuc5=5cv(nA*YDmqSmrPt=ukJcbu)&Z<;-6@RD5ab5tTY{M?t=#odq>_Mobi5}ALeBd8^h+~`0tG!=o5tu|p&mh$>CsB^?FIjK;(Rxj45r<7l0@3TM)uF; zy9U@{Tok|gDFy%-pqc_H0njDk(je=@cOtRCO%xyisi7#dCne3(i9Z3)stVqWA#XUk z?6gi=v)>5t&yo<2E8Y5NBEeVwA~MvlJqSVIJ8I4YgdSru96Baa=wOMEfQ}F`v0{Z% z{i!pYUqpYQ7B`g?^ZUm(HUBJO{D14mZ+^M~0DPz>{~Z8;Wryb4>JSIO3EYGQ|N0;e zoY4~thWcj?4pk=0d?@Plm$QG;{|g2F!4DFk3A_yezyP?fjmQ8166np})F1#dy8p>< zRHr0fyZ;xu{b}=W1^&Sg@E1R1002}j&9Bd20RRl>O#m1G0D|?pf9+53&zwlN|Czn} z@9*0``1!Z}$F}jV5&jDLul?(w0eamS|J)il{&Tzc-`>0bgHSmA(1Nij~)PH*g z{sj1cqQL*c58%)Kng1th{_CClho67FYyMvu@gIKvE7knhtLqOx|9aQ_zcS)K{QOs{ S`L9>kAAbJzuK9mu#Qy`3*}94V literal 0 HcmV?d00001 diff --git a/src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 b/src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5f3ede4ced7564e052752c385532dba26adcaa0d GIT binary patch literal 28864 zcmeIZ1yEc~yQsT{83xJV1czY3WpF2W&@i|YT!UMH5C#hpAh?s@4#8ap4ek;k5J3V2 zNrEK!O}_8ncb{`>*FIIJzB={a6MCj*b#?dZ?)T}ZmeiW1B*%va{7QGcwY9VWfCx}^ zv9-6PMYy}r>eKSTx%jz+W!2RHzz(o>wzb#xa(aQNR{^78yEBO=TD zcgf$gfy)R;%m2Rxe`WaPW&hRoSCjv_>i0hXP04R2H|d7J4S^d1Hw117+z_}Sa6{mR zzzu;L0yhM12>jnd;QG8?>aSRYkbiBUU;o+wtAB4OQGai5es2i;mjr(MgZf=_sy3sIS;Mq-n>wMi-(R>VDjt6zohT+#|!<&OPWz;RxYyM0m_Ymr{s8Z1NC z#C4|`38BKqK-DMOgWTQurGv;`LhK@mxgy}7BGhuo2np)@v2ha%09gG;eN@6;tzoCB zF_YlX1@7y?hb0?&Et5fv8u&5$(-)V$n6;#Wk#ry`}HZa4op_Fa4p~m)eKjjp?1fpB)rh)3TR8 zu+Z7ZO*Bmvu}vK5R`E1);(O+kQr}t>ie4x@zRPt= z>2%}PCuRJPBs5j(1EI-T4tk7%g^!M%qBi?_+;()Sh5bod?kqaS%SL3uD+IaA#QW9t z_r%HVZYIIuk6RuAH%}EDr~t$#4}9%Z2kv25^Yq2 zvn;!Qv0CrDW?r3|59l?|=sMvt)Qehq=N{Xo&+YZO%}R3K5m>m>@}4G4hiZzD@=+;y z1OZvFTDpyP+A2XB$(=L{{F+TVgBtB7*vlSO!Za-6)xj*>p)lUBo2J&?AL$-_{ZQ5- zz0Q#Kvw6&3ht>R3#t-N-38kfZthd^{az6VF^5K@n&3AJt(j-3)Q|w27OwogPh@b1m zq>(9nr{Y|ct67@bWY8XA``aIkp*RV2FC=h2^*N`i8qjr+#wGVgqM;P&dbuWn71Q$= zcIOJ}SA7=V!-VOs(*ciQE14STKQinGOXEkr;IVnHqY5*bJTmpoj5Q{z>e*cUzwEwt z8pvt0UpUj*ZLT2<8O&d4JJ_cSnAktUo663L%5L(yPCFZLfmcQ~$B#Y}uJOQKb9}$~ z@W<10_JAD~Zwf$mBq$sctVpI|XBQOy9rTs_K&=Z@&`MoGMrsB;s{~{GB+7wfNa2Ao znsESPG*wX#*GEspKZt|PZ~@g+?vlh?$fRP`B4d86oI@IpK_>SB1r`Ae>rx$7Hoy4{ z{$y#d+Eg{u#ya+*`m4e=fo;J9lWENrv7I^r=fuD{zq!0GWjnf;C-W)6OFJeFTJue9 z67z{Bs`b=5O%B;c5(}NzCX-G_6veF>Z4LW{=Z)j9k?=z^^Il*aN^D4JW`2o$0)4{Z zhO*}icZQt!8`pi zM-8g~trmA!Go^T^G$s8_CB((hPW0B6cKPL$Zuz34C#hD>a;~RWRk9x|*O&P&y|U5O z7{f;0Lca1ZHru90i^}(%F9lcF%F9(MP0O}L&uh~$99BDgq+h8uS__ul@v%yEF)a5+ z!jb480Ykl3{s+$q_^{=2cqF73;1%5ka5t1PoJ>j(P6UMiLQDjQB1z#ODF_Q-KxWj@ zpvFyAIzXb_!sf-1^4lLeB-nvKHay@15F{gPcF+#sAo*Z;+l<{QQ1hTm#i)QS09t+nUh!I_5RbAE4 zj5eGMSLJc_h0#`%L}S45qtQ{!s%WlIRWw?RQJ@+P3rWucpuZZKX5->0v1rgJEL0tH znh!7QPZ%MrW|{srOw?|m?f|OzmL%(}T;4JM)=F!tK$6u~#V)Y?e#uzigR9fqUqmui z-)pizU5_eGZ>$3!VS9Mz%XpB<1$tnnnfGFil3PMJ&<}Os`*8wg(m_E(K_E?q(P&V_ z?r_D1p&Ab8C3Q?LQTT%K_hKmIW02rKrPH_3kIxu3NVBb>LCr^ixb$9LH#w>eR5$8 zf_@mHM>o;fN+%8tF`~_)`bE@XyI%!vvYqJIrR8##lFdlH6g^)#Cqfy0q&iZwdgf>N z!bMy8xTR|gPKCxIpmAIUCktNW=?76sq_0&fiO|nr(CH{?2t73S+u>rw;c%p?ufY-I zWodLOENxt|cfXH)@2uqEw))wUZr8qxt-AU-;>cw0K<1l#piNR^NiPUR*Q;~iI3G;K zpGftUrnf;s{4xBw*EN2lo{PQ zJM!ect}AQpNcR5ik2(^q0!udJ{Bh2MZWh--d*WML>@d8Kj6qtEkKGq(arg9Ug#4mu zrKDmWy&xUc_)t4)AjLgO?23RvzoN(9V)%Zke>WcwYfcudVJ=o*(jnYt@6l^?JZmCE ze_F0!_?)a}!s5_Ls@CHqKhPvGvtjVj!tDmG&YjC9=N<7KH9Z%S@lQn1pG2~nV1+~c z62=)4TI@P7FF#a5Sv@0M0LQI23Et*1=KtoE{U+stprA!&Lm$| zHah4zRV7^#I4pbVY%T5|zfMrUx^@TPXZ=NdFTFH*dV3||wn6?9XK&|2W*8!);RH-r z(SWoS4`R1Hj9Aponk11`zx?&nTXe?!n8LwiEK_ex=LR@?#z<}}tB5Ro6P-LMcPpD) zaHQ<4T4FR0yC;)6c@F!mb9+|)d6VJDt*^U&R3YoCl5Tb}XcBVK4%G)RD!^rw{HtU% z427<~gkv_FK&Y{3fwYiiT1+Y7H+TUx4UVuGM{8iwSdSGG5EkKlys%nV;~pu`*_?@+ zSQ$wYALr<^TchB7yJNxxLiCKL$kbIolk=gBr6tw?ixvTkHn_4pwcj58E|Q0*){~jn zFes5nxZ4;zDA^yVYOjfvO6=Az^MG8Yy z;r@2Pr2H~*YhBA;Pu5DU${l3FcvS?n9+ngvFMMB+3B(;}867XmAd&YT?&6RU@8ft~ z!R;dKyJ?-}!Ig~t#Ot>|H2~NTvnS-@agIl~A|jBLkw|F_f*0*Htk!JS{Vk?tM4chb zA%rtcR|2-YcN9pZWn|sdwn??>7oa%GBYiP?r_FXW9VtA@6^A*BVJ3${0b~#qC=E)B zK^lYz0zjUX290GOo5K?wX_P&i6Fn{t2$enai9b=Ms&L0m4%=;NUQM1oZ&XD@mC6cl zn1A=Y=;BoQ!O+29aN1*hF`PhR^ZDIgfU~JJYr)`1*rS*Canx%!Cbr2qmxzwv{czC4 znST-OqU|wjSbQv_RQDDhf;#8V&fMbJLruXQkb`hV6f2w-z#{>@wF9GELEGaKcSUUL z@t-kBmn~Fs5X+V2k-ebRdeU8HmP@?^e0h=%Tg++1(uIsWKUA(N*orb;(-cQISaLei2zVUJUFfv9J z{bZpEf0I!5mH~%%fS`oJ#!pKCDlUvU0L#I$`E}-Hf=EG&K>C2J6fuCL)IARN_Bq!k z2f)re+PWUkWW^-JR~ARAjO|cG{ij%hvP=&?mKM}iPd?|fhw|ym7d|Rv{qo9P`N^i2 zIgyXAikcds>-V}>s!O-`hDbJQ#tG#v`MNUIuTnZjN6HYNi$9l|L?`?3GCb3|*wUuj zyV!8uKt1W2HfIElKC*ogYjFtDH**JiI51d|1Arn6R0@fMLSfQd8l|Ov;lam8%&^IK zmum{}W-u*y>3F=5@$3BUPa_gC;wlYZqjXP;Q>1-j>8zzmhxXea%lkRfzx=Vhb&u!8 z3LQz@kVEpA3ZYy-=#7Et!Ks)jk4+-4u)CqsBa0;w#bW!8g{$24bY72hY353iGGh9* z_d*yOJ@*t(DO^4#ZFSdFjnTHnZqo0f3^ z{U_e{I#3^o(2Nd1kp?3n_rvihyXNw;6IQ5Nw^GJF7<8*E(N36TmW*Uha6K|uvL%p? z&z^I7uUXQc{#ot#yKvQR)Rf}k1NN}pRqslZ0QJV!Ud;QcCwZ>1l*w%p#M{~hqPzP} zapEz;{f4$#ZS8FaP5vX6Ol=K39qua=Z)>o!bn^R>RY1X@$hEKTSTEJqb^P18q_e=e5KWip{msq< z0V0nJnhOygtmcE97Nv_RJ`tO?hZEPFB${14;cFk4t0so*q2~EtY)u+pj>XhI!zl@d z66K94V5T`iAHXpbNZ-nb{Prgv2@%GT4q4>k4Y#&~`{t`^wBN(TTeroUL8k-l!sAfy z(*P8E8aRi6JoA-cIv9vE-|7pgTnl6kXLd!m13sC97tn;?p^Oqf|9s&i*4ev3Q8RwW zAZ>`b^1M57=gXZ5ud(x|ErCY?Pif3yPkjO^_en;MLn-fFHzmJL^?dsNaCz4}$$y&z z)|K(YpWSS_@BP*OL#1QEXx0(CvD7}8+yDx)$HbDmD^6^Yh*r(vAv{lDOdivxgLyj6 zqW?ThZF*s}T8;NJ&YJ|dbC03}SG5|cOmsoFSD%ts!L|W-)H_um!cm{=v>w|ugtSKM zNpkXiq@@tnFmtBRp4F}xYlEAVOr;OPxNffu1A!e7hKu!D#< z6k$~QZ2XLH8HzrQ?c94(<<(ZB#e#a3p9LDuY*Y?YSRZPVjGfef9>}El`_;+rb_mV`@flsM; zuRcqjf8OwWG64&qkE?CC5^t&JGhI24KPQ)a8zB@8XOJoo`1n2puF_q^L)gs=w~|Vx z1`1HOflzHgP_>YjpvYe_oGPMEftx634~-5d+Rsx8%EAyTyN*v3Hd_>`G86%Qk>=KN zx0iRwlK8382rM!aQ>(OJGuN|0uSC*fh%lx_M=zZCje6x^5-H7DLEdo8+0v6<&0t4q z_z+Xr%h_B7kW_`&nFzU z(YMU*z|hR-p39gf^ErY)A<~rhjfQLg@&^fF!0Q43eMgyR^ zpxx>;qrs4Owpc;AZwDAved%RW%9ihGWfu|X+bF)Jhj4IhW)ma|Hs;crI6LUhSLh|- zL3Nha%jqM-*k+AzmfkdcnKqI^YG>J@Ei3FCwX(HYXO9SZjZ9V>_qb||)z%pIW>G}b z_GtHH5yOaqNldbD?GLn4z`%Agi+UF&qI1hq5g z(a1JZ@K6nA2x3KH9&?^vWzkPau5iOL2L`^L5@k0lb|Kvu>i$KI~kSJ+<~<-F$&)$Sp;xu%0iI~ z4MTb8q>P(H4*shJV#-S~nBN0tuU|J*E&3>hG$)dkhNq__d`eGcH?NZMB&Wt4iqRKK z^TyPOi!XjOO5Fp0WHt3FLV=c)>?oI32Ix!XO?9Oi#|_oDbAGn1vpg4xTV8#R$j53KrBzGLwJF$9UZ^3*{XwNz zZsg13!I#ZfboGwjbUlq65+AH6kcNj{iu@o?K08vH*WF2u#GVRw)gcqaG>8~fAWMTv znNg6j18zYq=wuM&VgTLoJDi?J&&ZYfe*2RLfIA3!!OKdhnBWqG{|jNTpI{OgvEIiJNhySgdcdye;-_nxlY zz4JutWck`T4d@!SuNA_)Xj7}<&jkpGaP*2TE zFrb7v#%A8PY`}n=K{X0jltp7o0<`&Tylbik z8@aVxMD{#S+z4d8F&YU_S#4|QXo+XonLuE8+49a7s+5clu4i_vicI?%v*(3cOwU;* zX2YQJl&qef+6knF*#d%{<7K5jdVV`fj~1*MGfn&#m7T+PPAZQwznJV6dKvh!c?k}l z(|I~)Cm0D#7G$(yw;op$;~_24KY>E4m!M4Q$Xie|xHuUyVF?TX7qH481P2LnP%!}v zLK@ z&TH~%hGq?Eq)4x{yEzMKL@4wi>0P)lss}EJG=Kxhb2tf#5ClAR=eK3-v&@4M(p%F( z$v6I|zL=ru7^LL_(QmXSzl%`Lh-Xco+u&8>wNGkBB(2eN4@Nx~#-Im(OL zC=zE!la>$B`6B&3@NPoeV{?CRo{MwKq|4&*99W29=+Vjb{a>HI(-#1v=|d0ihwj?; zH?Ch%$n`#rub5HkC6rWOddN|fcOdoTpI97br&7ah*hJDQJkjye&8j&q__t|MCE83~Jp&)b* z5NH&E1;tXu#Duei4g!fx$wA<_99Rh^?k#j_l@3H}>F7aexq_WpvjV>yxhLFyt;=3B zhDASqblRTjmPn!D?9q%GMQ??!Qa4X{@W3P9k2=^buay`^$pvive>CQR^sX3(*kItO zv@|p7*)=6q#^&jYoGnm~?ENRCl-j(OuH?aJIXTj9@t5J4z(xV6WcwR6!M66)Z3dHp zvzUruiX%$mLJ%G=2l-%dk0!ekQSQUSM@!8Rme3@|N6~k0yOxs11d4V>jY%*ec&XZX zBe?iH+j&9*;d1N3cK`{RE#XiR_}jtJ2uqzBSfQ3=c>4gl_0cOR4TucIf=l;0C`U?0 z<_M1TMgQPN)o5||zRuTvY`wC1<5*&*klUZG{Wvb`502Wdz1g=%{Mk&qiu2AmyJF5A z57hm)6bg3u4IfYTFd(a75S+ijVg+Q;#c}8v~Qu^unYy7ii=u zatjlDo?g$rKcl>!(7kqO(;!~BjNWg`R==iRTFJHvXqkoyQdxWisarf(JOT4o=0UNa|Cw_xlnysjd|9W#r1aGW5zZpb1a(MrAj#dgJ~pT*^*xNP-t zE4p4)e$;nr3=v5`!$}h0DPfc%AmKp<xrBQ$=ULiTN}}I z8`r$M;+6RscO^OS_R&EA2`i?~M2!TVpJbr#UaZr&EzMF1wQa|>OysUj8#|kYpSA&W zyt5kTue&=>OZGQHs6Yv*LoEm$juZ@W`>e8lmkbk*H8i`;;RzIBH;K;`Iml&i*g3MimWhlGsY;sdm*)?Qc4YbM&(ml+|wP_{js;Gh3nZw?COkhybw+ zSgafMtS18D?;=dF#0kK1oVtf2%!4|`KQtBh;+Pu+{n-535t~_J%RH!r&wIP)zL<4E zQQ0FaMeF)556i~WjhZ|3#k!>Cgf=?L8P-P;^Ww7Hi{ASMx@@oaa5p%_=ss#UjzcX@ zvPSL+mQTDY<#0J3wm!RHQ>XZ8E^Xh(`na~43^qihTB0~9{lKri8(WQ4Szx=lpTSHj z3!sKRON@(Pu|U)WMchSfMS$@Eek_euI$6(G%xPriJ(I==)rELp6lt+tfss#fhsp3x zlbM^IM}2Krack~3yoN!UJ0AB8If|Z_Wj<#~I67WXnba3AMZc@j<^Aes`E#(OhD@Jm zB9xHx62Dc6K<-rhYE*S!vMH*_uNQ6h#Yg!tM#{yq#eBuJ40-a>^a-Zmc%P647wU}9 zBYC-IisQwCGi^t*rQG7T(vtDO=Ma2BlrSuy_BGx^ z0)`!wR;_}=S?fm&qY|}6=~oQ1(!?hl5O?Rc*W70Vh11%Rq=GQ52EkHc<}>tF&j}3P zO>_O2D)dR2KJuk5V%Kz}I5foH*D2*Hq}2 z&R|Xfc`Jnv_V4ZSrdO6i4d2%LS@hOYlO)xDDyMDB3ZpC2VJXSuDTyc%B%PQQ`t461 z0C}O<5rW72HK@l9Ve2c5cagFWs~(cMSNI{QBF0xx_gr&APcPaO6f}jZfxoSu6dk9M zw99cFnnvZNxvs@Y1>?b_l=j>;6KcL#nku@>&rnD_GPop&OGvkj>(g`3Sm5NpKVjr& znDJ)OAuboO+3!kbWaYE;g@Ybq5t1giNk=#4>2{<*`5;nuhPP}fpuqihA!)C{N42K| z0`B{U*PjnyE!V4ugHZ-XEMYCp8H$I)3hB04>x1vUU)}Lmyeb>v%u8|)pco)t730Iw~LPwRCXYSp!@R})O z7VJDXc8}S^{=5&0ELcbc!kLl~^i_%*Lx%QeyCQ)+q%SAA4+!DuW^pIPeI9~A7xalX z&I&G$G6>pI!KXnpL-`RQO6BGWHT>~dTWwf7OrnpyHFkEJ%G)xSiwc;F3T>R7 z@6J%xEv|H=U+s>S*ImzX@9tc!zP4#Nar|M^)+R7DG^&+Cj;Aq@QrPmK@Clt1&WvXI zm!zdP!lMK3ds-?U8$vd=k2KZ4P?e2XGZ>LX@LtT3JSSN-?krdeP`7_Pqx^)6E&J68 ze_vC>rHg6H#B1L02a5l(6tMUwxiyqo`V9VksJotmZ<3RKYXs_4+b9%4D6Uqwn)0pMCGNG`_Fahy8BX@1ZKD4 zn?&9vmYpfy-*=Rq4smoL7)Ufn(M`f+{YzVdS6 zTF9|>{5dJ(^nLEe(~40(<27mYzPv^nS|?s~O_SRNqq^Sx?X!?=%Z8X|OnQbQ8c8q7 zBy0xQ`xTz$bX$JRnM^CCld@lz%_~$P67@tSB_s*@Mk!nBI2s2xi1aDe$QPRis~PCQ z4^;^C-z?1~%b%03T%QRTLq>CyUxyGn83q0I z5QgIS@h2JyL8A}@tMc@Cgkd198B}rGgZE*K_A+-8se=;JS&{G*yyx(WtzNX|XZkzC zc>Hv*XJM+5p^;d$kMXf3Lhz_O)Dj)g5k1?6+Pu8yG-HttgazDpyv7}uWeJqElPzDb zz7b5x3*}@VFzI+<&7qVTmxwl6q4V-}7PCebN(upzntDu#57%bNbrT5=(%>i6cH)R4(=dTUmz*Q z8X^>%SsG^r8Aa1JCi5*rThxNo^JzDPPN+zXaMtN>jH12!**m7KmJA<-I=tMmZE8C- z^tj_w-gfBQ60YAZmPq0M&2;7Un0=At9)&TEAT@ncM`(9_>UM`ElZ*ix{x^4e;Uj;L zmz!iN2i0$Xa*>cH9HJqrycP*X7zk?yV@>?#9ayz}6c6!=bW#jYe#ExJpxHfhG?YA| zTS^EpZy=^NbEhU#wMNg6J4(AFZNWz#1%AO}F-2R^FF$Z=iztj7Pdrn_L3_&d?QsEn z*lI&ruUh-e=De!wY5#8OUH7Kf5-qn&R~Pr9bszkw(%<6!#y-5{WWQ_r#ktD3rgVqz z1&J;jDcp{y&w;ZV4I?-tD6WJ?!7S8D($tX(k2ER_my>A$lMGP}-+}k6D3Dq33=mcc ze@cgW+lA(=v%#o8i1A=5VdCILwascN6VYTe3>%h-nVg$OC$w@VtQtD&*a9LZd+sTY`amp9xc0%7I;^t-4Ria}n zcP7WfHn`=>TfATxHE6}ap z+-73r%WVm*@BNp1y7sx}3_6#YF~tN+Fuu z-Wb;Y(8sK2j8z%_?N1y45uy_VGxKVO^t~~&XHeEKPTRrxb^hW=Fp+*n&f4!G7ru3C zOuhLu?Lj77nZ(R1=Qh85R*86je}2gbg-7;dlZ~?4!dPz(m~sf3TMC1U-6xHrbc&Mp zY>tt-V7CpA(&>CrZKV<()#h7FNJx4|aA#Z+e(N~7nF#b-*J=xibM)JRz^YUhBS$YZ~7PZCeRf!`G*L#!5 zhAnFpbTZ4L>P);Z7EC&+=S?Cpwc%m(X$6LU^2IGf`|h{0f(LSHN_ScP2DpvyvX98| zPQkoosZ-xr|0w!w5NX;MYccL@E9BGUQ);_2e$LvOwBmT_cqw)!a50G;ML|XZdF`jI zp|HY1ont{xFHX=GJ`<-hVlF9gU+32oEG%{t{oc~ZxF(ia@1*vSQ5*j4vx3X?fS8J( zF9p(F%^h*`N*MTHd*bMiDa-1uT_wvuHHL_(S9}%I^qw0XS1){QRNY}a^O$?N&H2_+ zdwGA0?OedU`zNbSLEpP{-UjH+e7)Cr+?fa#pasJU&k6z-b!;iJJPG@CNU82rdwDS9 zjT4@J!JBNxstM1W@-ldkoU^x`V*Vh>QB0MKy71dx(d>yx0v(R;7zxC_yjFkSAUP$V z$jbh;eP!K9?f9aNLfe6(n!u&w(+R=xj;w&HbE7MdM+fICeAnY&vflH)KMdIKYW?hW zu{gsLy={@LHb51W8xKOt{PrgX2~lNk19R|N1T%g%vwx zgd|VqQDjfwGdH?8gtoP`v|TrXfHsGi5=5(yEytJ=rpcinrwy5mFM`oeZDS!b#36DP z_pZ6910Yt%^QEIcOPs91S0 z7@{>ARsSt?MclqW^_{f!0waI35e3&0$6Y5vEXrI+=%Zk1W}b2iX%#kUbTJMjvyUb9 zxRJrQiNa?t5C0b3AKoZR3O1tajFvEYY3U(WLZ2lgA3qDkk-n7*LEjo;@Yf%Ok}!g3 zT_x~?QIGX?2|_Ju>AN|c3(!L#_ZArAIBkBSOk20)}1zE z)ZUZIvI6#iyl3etU_I+k}wM-nvhW?OSyfVe@#bxw5349 zGLmrS*H<=vKFO^Nvkp-Mz`rj{y6MCda!wUcRo7f*Rqks8> z1vDXZ008ZS;8$^40H8u!{cQ~fkcpc7_XgoQ_XhdzJ;VRIXZJ6EAiw;f1ptr~$ycgh z0pp{s0!RP=BaKkMO8=dc&%g8J{(!mt%b)*v{}f5`74lcqf4#qq1{k66{{RR#A^vX@ z`2Y9=+`Rw(zm4*b-0{YrKXTdsv7m4K`NvTHksaOm^G7cGKNj?jKmQoYKeD46fBwj2 z|Hp#9@#h~y`A2qie{JG$}bk6iYDEa)45{xOt)WJmuG3DFG+ literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/error.mp3 b/src/vs/platform/audioCues/browser/media/error.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/error.mp3 rename to src/vs/platform/audioCues/browser/media/error.mp3 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/foldedAreas.mp3 b/src/vs/platform/audioCues/browser/media/foldedAreas.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/foldedAreas.mp3 rename to src/vs/platform/audioCues/browser/media/foldedAreas.mp3 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/quickFixes.mp3 b/src/vs/platform/audioCues/browser/media/quickFixes.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/quickFixes.mp3 rename to src/vs/platform/audioCues/browser/media/quickFixes.mp3 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/taskCompleted.mp3 b/src/vs/platform/audioCues/browser/media/taskCompleted.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/taskCompleted.mp3 rename to src/vs/platform/audioCues/browser/media/taskCompleted.mp3 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/taskFailed.mp3 b/src/vs/platform/audioCues/browser/media/taskFailed.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/taskFailed.mp3 rename to src/vs/platform/audioCues/browser/media/taskFailed.mp3 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/terminalBell.mp3 b/src/vs/platform/audioCues/browser/media/terminalBell.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/terminalBell.mp3 rename to src/vs/platform/audioCues/browser/media/terminalBell.mp3 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/warning.mp3 b/src/vs/platform/audioCues/browser/media/warning.mp3 similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/warning.mp3 rename to src/vs/platform/audioCues/browser/media/warning.mp3 diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index fbe1d5a8853..d38d510d380 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -129,7 +129,6 @@ Registry.as(WorkbenchExtensions.Workbench).regi Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DynamicEditorConfigurations, LifecyclePhase.Ready); registerEditorContribution(FloatingClickMenu.ID, FloatingClickMenu); - //#endregion //#region Quick Access diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index 3b400d3fafc..84e1a657153 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -5,8 +5,8 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorunWithStore } from 'vs/base/common/observable'; +import { IAudioCueService, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; export class AudioCueLineDebuggerContribution diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index 490fab6c4c6..c3a895a3ecb 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -14,9 +14,9 @@ import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { autorun, autorunDelta, constObservable, debouncedObservable, derived, IObservable, observableFromEvent, observableFromPromise, wasEventTriggeredRecently } from 'vs/base/common/observable'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export class AudioCueLineFeatureContribution extends Disposable diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 2fc361426fd..bcfbc1fae7f 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ShowAudioCueHelp } from 'vs/workbench/contrib/audioCues/browser/commands'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IAudioCueService, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { AudioCueLineDebuggerContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution'; import { AudioCueLineFeatureContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution'; -import { AudioCueService, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; -import { ShowAudioCueHelp } from 'vs/workbench/contrib/audioCues/browser/commands'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); @@ -87,6 +87,14 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), ...audioCueFeatureBase, }, + 'audioCues.diffLineInserted': { + 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in diff review mode"), + ...audioCueFeatureBase, + }, + 'audioCues.diffLineDeleted': { + 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in diff review mode"), + ...audioCueFeatureBase, + }, } }); diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index ab6b124bfce..834c481cc5d 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -7,9 +7,9 @@ import { Codicon } from 'vs/base/common/codicons'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2 } from 'vs/platform/actions/common/actions'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class ShowAudioCueHelp extends Action2 { diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 1cd411eb22c..38ea286744c 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -15,11 +15,11 @@ import { InlayHintItem, asCommandLink } from 'vs/editor/contrib/inlayHints/brows import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Link } from 'vs/platform/opener/browser/link'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; export class InlayHintsAccessibility implements IEditorContribution { diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index 762f9f6f578..7c88571ea9f 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -15,7 +15,7 @@ import { ITerminalStatus } from 'vs/workbench/contrib/terminal/browser/terminalS import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { IMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; interface ITerminalData { terminal: ITerminalInstance; diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts index 76f88556c89..1b29fa27657 100644 --- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts @@ -46,7 +46,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; +import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; interface IWorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index 11239dc5fa2..f15422f2768 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -5,9 +5,9 @@ import { ok } from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { ACTIVE_TASK_STATUS, FAILED_TASK_STATUS, SUCCEEDED_TASK_STATUS, TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; import { AbstractProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { CommonTask, ITaskEvent, TaskEventKind, TaskRunType } from 'vs/workbench/contrib/tasks/common/tasks'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3ef386a0319..73b1c1fa40b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -55,7 +55,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks'; import { IDetectedLinks, TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; @@ -89,6 +88,7 @@ import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm'; +import { IAudioCueService, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; const enum Constants { /** diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts index 2694519e7d3..7e64705a746 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts @@ -11,7 +11,6 @@ import { asArray } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { ITerminalQuickFixOpenerAction, ITerminalQuickFixOptions, TerminalQuickFixAction, TerminalQuickFixMatchResult } from 'vs/workbench/contrib/terminal/browser/terminal'; import { DecorationSelector, updateLayout } from 'vs/workbench/contrib/terminal/browser/xterm/decorationStyles'; import { IDecoration, Terminal } from 'xterm'; @@ -24,6 +23,7 @@ import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/comm import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal'; import { URI } from 'vs/base/common/uri'; import { gitCreatePr, gitPushSetUpstream, gitSimilar } from 'vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; From 8007c639810ac284733e1ee3acc901d5b480e706 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 16 Nov 2022 15:42:41 -0800 Subject: [PATCH 22/67] Kernel mru picker preparation (#166513) * MRU skeleton * Kernel Source Provider * filter * register provider in exthost * Misc changes * Revert to suggestions * non jupyter controllers * Add ability to select a kernel * show extension name in detail. * :lipstick: * Move strategies out. * Back button * First arg be the notebook document * :lipstick: * Update kernel status * Show progress in MRU * remove separator header * Allow selecting 3rd party controllers * Misc * Fix spinner * Add separators in kernel picker * Update kernel select title. * Prep for MRU. Co-authored-by: Don Jayamanne --- .../api/browser/mainThreadNotebookKernels.ts | 25 +- .../workbench/api/common/extHost.api.impl.ts | 5 + .../workbench/api/common/extHost.protocol.ts | 4 + .../api/common/extHostNotebookKernels.ts | 38 +- .../api/common/extHostTypeConverters.ts | 13 + src/vs/workbench/api/common/extHostTypes.ts | 9 + .../services/notebookKernelServiceImpl.ts | 32 +- .../notebookKernelQuickPickStrategy.ts | 745 ++++++++++++++++++ .../browser/viewParts/notebookKernelView.ts | 553 +------------ .../contrib/notebook/common/notebookCommon.ts | 7 + .../notebook/common/notebookKernelService.ts | 8 + .../vscode.proposed.notebookKernelSource.d.ts | 21 + 12 files changed, 924 insertions(+), 536 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 08a02f4e0f1..26badff38b9 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -15,7 +16,7 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelDetectionTask, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IKernelSourceActionProvider, INotebookKernel, INotebookKernelChangeEvent, INotebookKernelDetectionTask, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -113,6 +114,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape private readonly _kernels = new Map(); private readonly _kernelDetectionTasks = new Map(); + private readonly _kernelSourceActionProviders = new Map(); private readonly _proxy: ExtHostNotebookKernelsShape; private readonly _executions = new Map(); @@ -309,4 +311,25 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape this._kernelDetectionTasks.delete(handle); } } + + // --- notebook kernel source action provider + + async $addKernelSourceActionProvider(handle: number, notebookType: string): Promise { + const kernelSourceActionProvider: IKernelSourceActionProvider = { + viewType: notebookType, + provideKernelSourceActions: async () => { + return this._proxy.$provideKernelSourceActions(handle, CancellationToken.None); + } + }; + const registration = this._notebookKernelService.registerKernelSourceActionProvider(notebookType, kernelSourceActionProvider); + this._kernelSourceActionProviders.set(handle, [kernelSourceActionProvider, registration]); + } + + $removeKernelSourceActionProvider(handle: number): void { + const tuple = this._kernelSourceActionProviders.get(handle); + if (tuple) { + tuple[1].dispose(); + this._kernelSourceActionProviders.delete(handle); + } + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b1644b0eba7..ad6a46721f0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1152,6 +1152,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'notebookKernelSource'); return extHostNotebookKernels.createNotebookControllerDetectionTask(extension, notebookType); }, + registerKernelSourceActionProvider(notebookType: string, provider: vscode.NotebookKernelSourceActionProvider) { + checkProposedApiEnabled(extension, 'notebookKernelSource'); + return extHostNotebookKernels.registerKernelSourceActionProvider(extension, notebookType, provider); + }, onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension, 'notebookCellExecutionState'); return extHostNotebookKernels.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables); @@ -1345,6 +1349,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity, NotebookControllerAffinity2: extHostTypes.NotebookControllerAffinity2, NotebookEdit: extHostTypes.NotebookEdit, + NotebookKernelSourceAction: extHostTypes.NotebookKernelSourceAction, PortAttributes: extHostTypes.PortAttributes, LinkedEditingRanges: extHostTypes.LinkedEditingRanges, TestResultState: extHostTypes.TestResultState, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b43131cb6d9..389a424dfdb 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1047,6 +1047,9 @@ export interface MainThreadNotebookKernelsShape extends IDisposable { $addKernelDetectionTask(handle: number, notebookType: string): Promise; $removeKernelDetectionTask(handle: number): void; + + $addKernelSourceActionProvider(handle: number, notebookType: string): Promise; + $removeKernelSourceActionProvider(handle: number): void; } export interface MainThreadNotebookRenderersShape extends IDisposable { @@ -2136,6 +2139,7 @@ export interface ExtHostNotebookKernelsShape { $cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise; $acceptKernelMessageFromRenderer(handle: number, editorId: string, message: any): void; $cellExecutionChanged(uri: UriComponents, cellHandle: number, state: notebookCommon.NotebookCellExecutionState | undefined): void; + $provideKernelSourceActions(handle: number, token: CancellationToken): Promise; } export interface ExtHostInteractiveShape { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 3107aa0eb0c..a58f8cec0be 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -5,13 +5,14 @@ import { asArray } from 'vs/base/common/arrays'; import { DeferredPromise, timeout } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { Cache } from 'vs/workbench/api/common/cache'; import { ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookOutputDto } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; @@ -20,7 +21,7 @@ import { ExtHostCell } from 'vs/workbench/api/common/extHostNotebookDocument'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { NotebookCellExecutionState as ExtHostNotebookCellExecutionState, NotebookCellOutput, NotebookControllerAffinity2 } from 'vs/workbench/api/common/extHostTypes'; import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webview'; -import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelSourceAction, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -47,6 +48,10 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { private _kernelDetectionTask = new Map(); private _kernelDetectionTaskHandlePool: number = 0; + private _kernelSourceActionProviders = new Map(); + private _kernelSourceActionProviderHandlePool: number = 0; + private _kernelSourceActionProviderCache = new Cache('NotebookKernelSourceActionProviderCache'); + private readonly _kernelData = new Map(); private _handlePool: number = 0; @@ -292,6 +297,33 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { return detectionTask; } + registerKernelSourceActionProvider(extension: IExtensionDescription, viewType: string, provider: vscode.NotebookKernelSourceActionProvider) { + const handle = this._kernelSourceActionProviderHandlePool++; + const that = this; + + this._kernelSourceActionProviders.set(handle, provider); + this._logService.trace(`NotebookKernelSourceActionProvider[${handle}], CREATED by ${extension.identifier.value}`); + this._proxy.$addKernelSourceActionProvider(handle, viewType); + + return { + dispose: () => { + this._kernelSourceActionProviders.delete(handle); + that._proxy.$removeKernelSourceActionProvider(handle); + } + }; + } + + async $provideKernelSourceActions(handle: number, token: CancellationToken): Promise { + const provider = this._kernelSourceActionProviders.get(handle); + if (provider) { + const disposables = new DisposableStore(); + this._kernelSourceActionProviderCache.add([disposables]); + const ret = await provider.provideNotebookKernelSourceActions(token); + return (ret ?? []).map(item => extHostTypeConverters.NotebookKernelSourceAction.from(item, this._commands.converter, disposables)); + } + return []; + } + $acceptNotebookAssociation(handle: number, uri: UriComponents, value: boolean): void { const obj = this._kernelData.get(handle); if (obj) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index ff023d1fc3b..bc889cd13cf 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1705,6 +1705,19 @@ export namespace NotebookStatusBarItem { } } +export namespace NotebookKernelSourceAction { + export function from(item: vscode.NotebookKernelSourceAction, commandsConverter: Command.ICommandsConverter, disposables: DisposableStore): notebooks.INotebookKernelSourceAction { + const command = typeof item.command === 'string' ? { title: '', command: item.command } : item.command; + + return { + command: commandsConverter.toInternal(command, disposables), + label: item.label, + description: item.description, + detail: item.detail + }; + } +} + export namespace NotebookDocumentContentOptions { export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions { return { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 17aee78078b..858a37b9a8e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3617,6 +3617,15 @@ export class NotebookRendererScript { } } +export class NotebookKernelSourceAction { + description?: string; + detail?: string; + command?: vscode.Command; + constructor( + public label: string + ) { } +} + //#endregion //#region Timeline diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts index 97b33b0a540..e6024820012 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts @@ -5,8 +5,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike, ISourceAction, INotebookSourceActionChangeEvent, INotebookKernelDetectionTask } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelSourceAction, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike, ISourceAction, INotebookSourceActionChangeEvent, INotebookKernelDetectionTask, IKernelSourceActionProvider } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; @@ -107,6 +107,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private readonly _kernelSources = new Map(); private readonly _kernelDetectionTasks = new Map(); private readonly _onDidChangeKernelDetectionTasks = this._register(new Emitter()); + private readonly _kernelSourceActionProviders = new Map(); readonly onDidChangeSelectedNotebooks: Event = this._onDidChangeNotebookKernelBinding.event; readonly onDidAddKernel: Event = this._onDidAddKernel.event; @@ -366,4 +367,31 @@ export class NotebookKernelService extends Disposable implements INotebookKernel getKernelDetectionTasks(notebook: INotebookTextModelLike): INotebookKernelDetectionTask[] { return this._kernelDetectionTasks.get(notebook.viewType) ?? []; } + + registerKernelSourceActionProvider(viewType: string, provider: IKernelSourceActionProvider): IDisposable { + const providers = this._kernelSourceActionProviders.get(viewType) ?? []; + providers.push(provider); + this._kernelSourceActionProviders.set(viewType, providers); + + return toDisposable(() => { + const providers = this._kernelSourceActionProviders.get(viewType) ?? []; + const idx = providers.indexOf(provider); + if (idx >= 0) { + providers.splice(idx, 1); + this._kernelSourceActionProviders.set(viewType, providers); + } + }); + } + + /** + * Get kernel source actions from providers + */ + getKernelSourceActions2(notebook: INotebookTextModelLike): Promise { + const viewType = notebook.viewType; + const providers = this._kernelSourceActionProviders.get(viewType) ?? []; + const promises = providers.map(provider => provider.provideKernelSourceActions()); + return Promise.all(promises).then(actions => { + return actions.reduce((a, b) => a.concat(b), []); + }); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts new file mode 100644 index 00000000000..b7940b1b9ab --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -0,0 +1,745 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./notebookKernelActionViewItem'; +import { groupBy } from 'vs/base/common/arrays'; +import { createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Codicon } from 'vs/base/common/codicons'; +import { Event } from 'vs/base/common/event'; +import { compareIgnoreCase, uppercaseFirstLetter } from 'vs/base/common/strings'; +import { localize } from 'vs/nls'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IQuickInputService, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { getNotebookEditorFromEditorPane, INotebookEditor, INotebookExtensionRecommendation, JUPYTER_EXTENSION_ID, KERNEL_RECOMMENDATIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { Command } from 'vs/editor/common/languages'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { IAction } from 'vs/base/common/actions'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; + +type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; +function isKernelPick(item: QuickPickInput): item is KernelPick { + return 'kernel' in item; +} +type SourcePick = IQuickPickItem & { action: ISourceAction }; +function isSourcePick(item: QuickPickInput): item is SourcePick { + return 'action' in item; +} +type InstallExtensionPick = IQuickPickItem & { extensionId: string }; +function isInstallExtensionPick(item: QuickPickInput): item is InstallExtensionPick { + return item.id === 'installSuggested' && 'extensionId' in item; +} +type KernelSourceQuickPickItem = IQuickPickItem & { command: Command }; +type KernelQuickPickItem = IQuickPickItem | InstallExtensionPick | KernelPick | SourcePick | KernelSourceQuickPickItem; +const KERNEL_PICKER_UPDATE_DEBOUNCE = 200; + +export type KernelQuickPickContext = + { id: string; extension: string } | + { notebookEditorId: string } | + { id: string; extension: string; notebookEditorId: string } | + { ui?: boolean; notebookEditor?: NotebookEditorWidget }; + +export interface IKernelPickerStrategy { + showQuickPick(context?: KernelQuickPickContext): Promise; +} + +function getEditorFromContext(editorService: IEditorService, context?: KernelQuickPickContext): INotebookEditor | undefined { + let editor: INotebookEditor | undefined; + if (context !== undefined && 'notebookEditorId' in context) { + const editorId = context.notebookEditorId; + const matchingEditor = editorService.visibleEditorPanes.find((editorPane) => { + const notebookEditor = getNotebookEditorFromEditorPane(editorPane); + return notebookEditor?.getId() === editorId; + }); + editor = getNotebookEditorFromEditorPane(matchingEditor); + } else if (context !== undefined && 'notebookEditor' in context) { + editor = context?.notebookEditor; + } else { + editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + } + + return editor; +} + +function toQuickPick(kernel: INotebookKernel, selected: INotebookKernel | undefined) { + const res = { + kernel, + picked: kernel.id === selected?.id, + label: kernel.label, + description: kernel.description, + detail: kernel.detail + }; + if (kernel.id === selected?.id) { + if (!res.description) { + res.description = localize('current1', "Currently Selected"); + } else { + res.description = localize('current2', "{0} - Currently Selected", res.description); + } + } + return res; +} + + +abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { + constructor( + protected readonly _notebookKernelService: INotebookKernelService, + protected readonly _editorService: IEditorService, + protected readonly _productService: IProductService, + protected readonly _quickInputService: IQuickInputService, + protected readonly _labelService: ILabelService, + protected readonly _logService: ILogService, + protected readonly _paneCompositePartService: IPaneCompositePartService, + protected readonly _extensionWorkbenchService: IExtensionsWorkbenchService, + protected readonly _extensionService: IExtensionService, + protected readonly _commandService: ICommandService + ) { } + + async showQuickPick(context?: KernelQuickPickContext): Promise { + const editor = getEditorFromContext(this._editorService, context); + + if (!editor || !editor.hasModel()) { + return false; + } + let controllerId = context && 'id' in context ? context.id : undefined; + let extensionId = context && 'extension' in context ? context.extension : undefined; + + if (controllerId && (typeof controllerId !== 'string' || typeof extensionId !== 'string')) { + // validate context: id & extension MUST be strings + controllerId = undefined; + extensionId = undefined; + } + + const notebook = editor.textModel; + const scopedContextKeyService = editor.scopedContextKeyService; + const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const { selected, all } = matchResult; + + if (selected && controllerId && selected.id === controllerId && ExtensionIdentifier.equals(selected.extension, extensionId)) { + // current kernel is wanted kernel -> done + return true; + } + + let newKernel: INotebookKernel | undefined; + if (controllerId) { + const wantedId = `${extensionId}/${controllerId}`; + for (const candidate of all) { + if (candidate.id === wantedId) { + newKernel = candidate; + break; + } + } + if (!newKernel) { + this._logService.warn(`wanted kernel DOES NOT EXIST, wanted: ${wantedId}, all: ${all.map(k => k.id)}`); + return false; + } + } + + if (newKernel) { + this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); + return true; + } + + const quickPick = this._quickInputService.createQuickPick(); + const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); + quickPick.items = quickPickItems; + quickPick.canSelectMany = false; + quickPick.placeholder = selected + ? localize('prompt.placeholder.change', "Change kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true })) + : localize('prompt.placeholder.select', "Select kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true })); + + quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0; + + const kernelDetectionTaskListener = this._notebookKernelService.onDidChangeKernelDetectionTasks(() => { + quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0; + }); + + // run extension recommendataion task if quickPickItems is empty + const extensionRecommendataionPromise = quickPickItems.length === 0 + ? createCancelablePromise(token => this._showInstallKernelExtensionRecommendation(notebook, quickPick, this._extensionWorkbenchService, token)) + : undefined; + + const kernelChangeEventListener = Event.debounce( + Event.any( + this._notebookKernelService.onDidChangeSourceActions, + this._notebookKernelService.onDidAddKernel, + this._notebookKernelService.onDidRemoveKernel, + this._notebookKernelService.onDidChangeNotebookAffinity + ), + (last, _current) => last, + KERNEL_PICKER_UPDATE_DEBOUNCE + )(async () => { + // reset quick pick progress + quickPick.busy = false; + extensionRecommendataionPromise?.cancel(); + + const currentActiveItems = quickPick.activeItems; + const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); + quickPick.keepScrollPosition = true; + + // recalcuate active items + const activeItems: KernelQuickPickItem[] = []; + for (const item of currentActiveItems) { + if (isKernelPick(item)) { + const kernelId = item.kernel.id; + const sameItem = quickPickItems.find(pi => isKernelPick(pi) && pi.kernel.id === kernelId) as KernelPick | undefined; + if (sameItem) { + activeItems.push(sameItem); + } + } else if (isSourcePick(item)) { + const sameItem = quickPickItems.find(pi => isSourcePick(pi) && pi.action.action.id === item.action.action.id) as SourcePick | undefined; + if (sameItem) { + activeItems.push(sameItem); + } + } + } + + quickPick.items = quickPickItems; + quickPick.activeItems = activeItems; + }, this); + + const pick = await new Promise((resolve, reject) => { + quickPick.onDidAccept(() => { + const item = quickPick.selectedItems[0]; + if (item) { + resolve(item); + } else { + reject(); + } + + quickPick.hide(); + }); + + quickPick.onDidHide(() => () => { + kernelDetectionTaskListener.dispose(); + kernelChangeEventListener.dispose(); + quickPick.dispose(); + reject(); + }); + quickPick.show(); + }); + + if (pick) { + return await this._handleQuickPick(notebook, pick, context); + } + + return false; + } + + protected abstract _getKernelPickerQuickPickItems( + notebookTextModel: NotebookTextModel, + matchResult: INotebookKernelMatchResult, + notebookKernelService: INotebookKernelService, + scopedContextKeyService: IContextKeyService + ): QuickPickInput[]; + + protected async _handleQuickPick(notebook: NotebookTextModel, pick: KernelQuickPickItem, context?: KernelQuickPickContext) { + if (isKernelPick(pick)) { + const newKernel = pick.kernel; + this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); + return true; + } + + // actions + if (pick.id === 'install') { + await this._showKernelExtension( + this._paneCompositePartService, + this._extensionWorkbenchService, + this._extensionService, + notebook.viewType + ); + // suggestedExtension must be defined for this option to be shown, but still check to make TS happy + } else if (isInstallExtensionPick(pick)) { + await this._showKernelExtension( + this._paneCompositePartService, + this._extensionWorkbenchService, + this._extensionService, + notebook.viewType, + pick.extensionId, + this._productService.quality !== 'stable' + ); + } else if (isSourcePick(pick)) { + // selected explicilty, it should trigger the execution? + pick.action.runAction(); + } + + return true; + } + + private async _showKernelExtension( + paneCompositePartService: IPaneCompositePartService, + extensionWorkbenchService: IExtensionsWorkbenchService, + extensionService: IExtensionService, + viewType: string, + extId?: string, + isInsiders?: boolean + ) { + // If extension id is provided attempt to install the extension as the user has requested the suggested ones be installed + if (extId) { + const extension = (await extensionWorkbenchService.getExtensions([{ id: extId }], CancellationToken.None))[0]; + const canInstall = await extensionWorkbenchService.canInstall(extension); + // If we can install then install it, otherwise we will fall out into searching the viewlet + if (canInstall) { + await extensionWorkbenchService.install( + extension, + { + installPreReleaseVersion: isInsiders ?? false, + context: { skipWalkthrough: true } + }, + ProgressLocation.Notification + ); + await extensionService.activateByEvent(`onNotebook:${viewType}`); + return; + } + } + + const viewlet = await paneCompositePartService.openPaneComposite(EXTENSION_VIEWLET_ID, ViewContainerLocation.Sidebar, true); + const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; + const pascalCased = viewType.split(/[^a-z0-9]/ig).map(uppercaseFirstLetter).join(''); + view?.search(`@tag:notebookKernel${pascalCased}`); + } + + private async _showInstallKernelExtensionRecommendation( + notebookTextModel: NotebookTextModel, + quickPick: IQuickPick, + extensionWorkbenchService: IExtensionsWorkbenchService, + token: CancellationToken + ) { + quickPick.busy = true; + + const newQuickPickItems = await this._getKernelRecommendationsQuickPickItems(notebookTextModel, extensionWorkbenchService); + quickPick.busy = false; + + if (token.isCancellationRequested) { + return; + } + + if (newQuickPickItems && quickPick.items.length === 0) { + quickPick.items = newQuickPickItems; + } + } + + private async _getKernelRecommendationsQuickPickItems( + notebookTextModel: NotebookTextModel, + extensionWorkbenchService: IExtensionsWorkbenchService, + ): Promise[] | undefined> { + const quickPickItems: QuickPickInput[] = []; + + const language = this.getSuggestedLanguage(notebookTextModel); + const suggestedExtension: INotebookExtensionRecommendation | undefined = language ? this.getSuggestedKernelFromLanguage(notebookTextModel.viewType, language) : undefined; + if (suggestedExtension) { + await extensionWorkbenchService.queryLocal(); + const extension = extensionWorkbenchService.installed.find(e => e.identifier.id === suggestedExtension.extensionId); + + if (extension) { + // it's installed but might be detecting kernels + return undefined; + } + + // We have a suggested kernel, show an option to install it + quickPickItems.push({ + id: 'installSuggested', + description: suggestedExtension.displayName ?? suggestedExtension.extensionId, + label: `$(${Codicon.lightbulb.id}) ` + localize('installSuggestedKernel', 'Install suggested extensions'), + extensionId: suggestedExtension.extensionId + }); + } + // there is no kernel, show the install from marketplace + quickPickItems.push({ + id: 'install', + label: localize('searchForKernels', "Browse marketplace for kernel extensions"), + }); + + return quickPickItems; + } + + /** + * Examine the most common language in the notebook + * @param notebookTextModel The notebook text model + * @returns What the suggested language is for the notebook. Used for kernal installing + */ + private getSuggestedLanguage(notebookTextModel: NotebookTextModel): string | undefined { + const metaData = notebookTextModel.metadata; + let suggestedKernelLanguage: string | undefined = (metaData.custom as any)?.metadata?.language_info?.name; + // TODO how do we suggest multi language notebooks? + if (!suggestedKernelLanguage) { + const cellLanguages = notebookTextModel.cells.map(cell => cell.language).filter(language => language !== 'markdown'); + // Check if cell languages is all the same + if (cellLanguages.length > 1) { + const firstLanguage = cellLanguages[0]; + if (cellLanguages.every(language => language === firstLanguage)) { + suggestedKernelLanguage = firstLanguage; + } + } + } + return suggestedKernelLanguage; + } + + /** + * Given a language and notebook view type suggest a kernel for installation + * @param language The language to find a suggested kernel extension for + * @returns A recommednation object for the recommended extension, else undefined + */ + private getSuggestedKernelFromLanguage(viewType: string, language: string): INotebookExtensionRecommendation | undefined { + const recommendation = KERNEL_RECOMMENDATIONS.get(viewType)?.get(language); + return recommendation; + } +} + +export class KernelPickerFlatStrategy extends KernelPickerStrategyBase { + + constructor( + @INotebookKernelService _notebookKernelService: INotebookKernelService, + @IEditorService _editorService: IEditorService, + @IProductService _productService: IProductService, + @IQuickInputService _quickInputService: IQuickInputService, + @ILabelService _labelService: ILabelService, + @ILogService _logService: ILogService, + @IPaneCompositePartService _paneCompositePartService: IPaneCompositePartService, + @IExtensionsWorkbenchService _extensionWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService _extensionService: IExtensionService, + @ICommandService _commandService: ICommandService + + ) { + super( + _notebookKernelService, + _editorService, + _productService, + _quickInputService, + _labelService, + _logService, + _paneCompositePartService, + _extensionWorkbenchService, + _extensionService, + _commandService, + ); + } + + protected _getKernelPickerQuickPickItems( + notebookTextModel: NotebookTextModel, + matchResult: INotebookKernelMatchResult, + notebookKernelService: INotebookKernelService, + scopedContextKeyService: IContextKeyService + ): QuickPickInput[] { + const { selected, all, suggestions, hidden } = matchResult; + + const quickPickItems: QuickPickInput[] = []; + if (all.length) { + // Always display suggested kernels on the top. + this._fillInSuggestions(quickPickItems, suggestions, selected); + + // Next display all of the kernels not marked as hidden grouped by categories or extensions. + // If we don't have a kind, always display those at the bottom. + const picks = all.filter(item => (!suggestions.includes(item) && !hidden.includes(item))).map(kernel => toQuickPick(kernel, selected)); + const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); + kernelsPerCategory.forEach(items => { + quickPickItems.push({ + type: 'separator', + label: items[0].kernel.kind || localize('otherKernelKinds', "Other") + }); + quickPickItems.push(...items); + }); + } + + const sourceActions = notebookKernelService.getSourceActions(notebookTextModel, scopedContextKeyService); + if (sourceActions.length) { + quickPickItems.push({ + type: 'separator', + // label: localize('sourceActions', "") + }); + + sourceActions.forEach(sourceAction => { + const res = { + action: sourceAction, + picked: false, + label: sourceAction.action.label, + }; + + quickPickItems.push(res); + }); + } + + return quickPickItems; + } + + private _fillInSuggestions(quickPickItems: QuickPickInput[], suggestions: INotebookKernel[], selected: INotebookKernel | undefined) { + if (!suggestions.length) { + return; + } + + if (suggestions.length === 1 && suggestions[0].id === selected?.id) { + quickPickItems.push({ + type: 'separator', + label: localize('selectedKernels', "Selected") + }); + + // The title is already set to "Selected" so we don't need to set it again in description, thus passing in `undefined`. + quickPickItems.push(toQuickPick(suggestions[0], undefined)); + return; + } + + quickPickItems.push({ + type: 'separator', + label: localize('suggestedKernels', "Suggested") + }); + quickPickItems.push(...suggestions.map(kernel => toQuickPick(kernel, selected))); + } + + static updateKernelStatusAction(notebook: NotebookTextModel, action: IAction, notebookKernelService: INotebookKernelService, scopedContextKeyService?: IContextKeyService) { + const detectionTasks = notebookKernelService.getKernelDetectionTasks(notebook); + if (detectionTasks.length) { + action.enabled = true; + action.label = localize('kernels.detecting', "Detecting Kernels"); + action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')); + return; + } + + const runningActions = notebookKernelService.getRunningSourceActions(notebook); + + const updateActionFromSourceAction = (sourceAction: ISourceAction, running: boolean) => { + const sAction = sourceAction.action; + action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon); + action.label = sAction.label; + action.enabled = true; + }; + + if (runningActions.length) { + return updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true); + } + + const info = notebookKernelService.getMatchingKernel(notebook); + if (info.all.length === 0) { + action.enabled = true; + const sourceActions = notebookKernelService.getSourceActions(notebook, scopedContextKeyService); + if (sourceActions.length === 1) { + // exact one action + updateActionFromSourceAction(sourceActions[0], false); + } else if (sourceActions.filter(sourceAction => sourceAction.isPrimary).length === 1) { + // exact one primary action + updateActionFromSourceAction(sourceActions.filter(sourceAction => sourceAction.isPrimary)[0], false); + } else { + action.class = ThemeIcon.asClassName(selectKernelIcon); + action.label = localize('select', "Select Kernel"); + action.tooltip = ''; + } + return; + } + + action.enabled = true; + action.class = ThemeIcon.asClassName(selectKernelIcon); + const selectedOrSuggested = info.selected + ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined) + ?? (info.all.length === 1 ? info.all[0] : undefined); + if (selectedOrSuggested) { + // selected or suggested kernel + action.label = selectedOrSuggested.label; + action.tooltip = selectedOrSuggested.description ?? selectedOrSuggested.detail ?? ''; + if (!info.selected) { + // special UI for selected kernel? + } + } else { + // many kernels or no kernels + action.label = localize('select', "Select Kernel"); + action.tooltip = ''; + } + } +} + +export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { + constructor( + @INotebookKernelService _notebookKernelService: INotebookKernelService, + @IEditorService _editorService: IEditorService, + @IProductService _productService: IProductService, + @IQuickInputService _quickInputService: IQuickInputService, + @ILabelService _labelService: ILabelService, + @ILogService _logService: ILogService, + @IPaneCompositePartService _paneCompositePartService: IPaneCompositePartService, + @IExtensionsWorkbenchService _extensionWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService _extensionService: IExtensionService, + @ICommandService _commandService: ICommandService + + ) { + super( + _notebookKernelService, + _editorService, + _productService, + _quickInputService, + _labelService, + _logService, + _paneCompositePartService, + _extensionWorkbenchService, + _extensionService, + _commandService, + ); + } + protected _getKernelPickerQuickPickItems(notebookTextModel: NotebookTextModel, matchResult: INotebookKernelMatchResult, notebookKernelService: INotebookKernelService, scopedContextKeyService: IContextKeyService): QuickPickInput[] { + const quickPickItems: QuickPickInput[] = []; + let previousKind = ''; + + if (matchResult.selected) { + const kernelItem = toQuickPick(matchResult.selected, matchResult.selected); + const kind = matchResult.selected.kind || ''; + if (kind) { + previousKind = kind; + quickPickItems.push({ type: 'separator', label: kind }); + } + quickPickItems.push(kernelItem); + } + + matchResult.suggestions.filter(kernel => kernel.id !== matchResult.selected?.id).map(kernel => toQuickPick(kernel, matchResult.selected)) + .forEach(kernel => { + const kind = kernel.kernel.kind || ''; + if (kind && kind !== previousKind) { + previousKind = kind; + quickPickItems.push({ type: 'separator', label: kind }); + } + quickPickItems.push(kernel); + }); + + quickPickItems.push({ + type: 'separator' + }); + + // select another kernel quick pick + quickPickItems.push({ + id: 'selectAnother', + label: localize('selectAnotherKernel.more', "Select Another Kernel..."), + }); + + return quickPickItems; + } + + protected override async _handleQuickPick(notebook: NotebookTextModel, pick: KernelQuickPickItem, context?: KernelQuickPickContext): Promise { + if (pick.id === 'selectAnother') { + return this.displaySelectAnotherQuickPick(notebook, context); + } + + return super._handleQuickPick(notebook, pick, context); + } + + private async displaySelectAnotherQuickPick(notebook: NotebookTextModel, context?: KernelQuickPickContext) { + const disposables = new DisposableStore(); + return new Promise(resolve => { + // select from kernel sources + const quickPick = this._quickInputService.createQuickPick(); + quickPick.title = localize('selectAnotherKernel', "Select Another Kernel"); + quickPick.busy = true; + quickPick.buttons = [this._quickInputService.backButton]; + quickPick.show(); + + const quickPickItems: QuickPickInput[] = []; + disposables.add(quickPick.onDidTriggerButton(button => { + if (button === this._quickInputService.backButton) { + quickPick.hide(); + resolve(this.showQuickPick(context)); + } + })); + disposables.add(quickPick.onDidAccept(async () => { + quickPick.hide(); + quickPick.dispose(); + if (quickPick.selectedItems) { + if ('command' in quickPick.selectedItems[0]) { + const selectedKernelId = await this._executeCommand(notebook, quickPick.selectedItems[0].command); + if (selectedKernelId) { + const { all } = await this._notebookKernelService.getMatchingKernel(notebook); + const kernel = all.find(kernel => kernel.id === `ms-toolsai.jupyter/${selectedKernelId}`); + if (kernel) { + await this._notebookKernelService.selectKernelForNotebook(kernel, notebook); + resolve(true); + } + resolve(true); + } else { + return resolve(this.displaySelectAnotherQuickPick(notebook)); + } + } else if ('kernel' in quickPick.selectedItems[0]) { + await this._notebookKernelService.selectKernelForNotebook(quickPick.selectedItems[0].kernel, notebook); + resolve(true); + } + } + })); + this._notebookKernelService.getKernelSourceActions2(notebook).then(actions => { + quickPick.busy = false; + const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const others = matchResult.all.filter(item => item.extension.value !== JUPYTER_EXTENSION_ID); + quickPickItems.push(...others.map(kernel => ({ + label: kernel.label, + detail: kernel.extension.value, + kernel + }))); + const validActions = actions.filter(action => action.command); + + quickPickItems.push(...validActions.map(action => { + return { + id: typeof action.command! === 'string' ? action.command! : action.command!.id, + label: action.label, + detail: action.detail, + description: action.description, + command: action.command + }; + })); + + quickPick.items = quickPickItems; + }); + }).finally(() => { + disposables.dispose(); + }); + } + + private async _executeCommand(notebook: NotebookTextModel, command: string | Command): Promise { + const id = typeof command === 'string' ? command : command.id; + const args = typeof command === 'string' ? [] : command.arguments ?? []; + + if (typeof command === 'string' || !command.arguments || !Array.isArray(command.arguments) || command.arguments.length === 0) { + args.unshift({ + uri: notebook.uri, + $mid: MarshalledId.NotebookActionContext + }); + } + + if (typeof command === 'string') { + return this._commandService.executeCommand(id); + } else { + return this._commandService.executeCommand(id, ...args); + } + } + + static updateKernelStatusAction(notebook: NotebookTextModel, action: IAction, notebookKernelService: INotebookKernelService) { + const detectionTasks = notebookKernelService.getKernelDetectionTasks(notebook); + if (detectionTasks.length) { + action.enabled = true; + action.label = localize('kernels.detecting', "Detecting Kernels"); + action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')); + return; + } + + const info = notebookKernelService.getMatchingKernel(notebook); + + if (info.selected) { + action.label = info.selected.label; + action.class = ThemeIcon.asClassName(selectKernelIcon); + action.tooltip = info.selected.description ?? info.selected.detail ?? ''; + } else { + action.label = localize('select', "Select Kernel"); + action.class = ThemeIcon.asClassName(selectKernelIcon); + action.tooltip = ''; + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 1e3f67378b1..6392cf58542 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -6,457 +6,21 @@ import 'vs/css!./notebookKernelActionViewItem'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; -import { groupBy } from 'vs/base/common/arrays'; -import { createCancelablePromise } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Codicon } from 'vs/base/common/codicons'; import { Event } from 'vs/base/common/event'; -import { compareIgnoreCase, uppercaseFirstLetter } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IQuickInputService, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ViewContainerLocation } from 'vs/workbench/common/views'; -import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { NOTEBOOK_ACTIONS_CATEGORY, SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { getNotebookEditorFromEditorPane, INotebookEditor, INotebookExtensionRecommendation, KERNEL_RECOMMENDATIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; -import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { KernelPickerFlatStrategy, KernelPickerMRUStrategy, KernelQuickPickContext } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy'; -type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; -function isKernelPick(item: QuickPickInput): item is KernelPick { - return 'kernel' in item; -} -type SourcePick = IQuickPickItem & { action: ISourceAction }; -function isSourcePick(item: QuickPickInput): item is SourcePick { - return 'action' in item; -} -type InstallExtensionPick = IQuickPickItem & { extensionId: string }; -function isInstallExtensionPick(item: QuickPickInput): item is InstallExtensionPick { - return item.id === 'installSuggested' && 'extensionId' in item; -} -type KernelQuickPickItem = IQuickPickItem | InstallExtensionPick | KernelPick | SourcePick; -const KERNEL_PICKER_UPDATE_DEBOUNCE = 200; - -type KernelQuickPickContext = - { id: string; extension: string } | - { notebookEditorId: string } | - { id: string; extension: string; notebookEditorId: string } | - { ui?: boolean; notebookEditor?: NotebookEditorWidget }; - -interface IKernelPickerStrategy { - showQuickPick(context?: KernelQuickPickContext): Promise; -} - -class KernelPickerFlatStrategy implements IKernelPickerStrategy { - constructor( - private readonly _notebookKernelService: INotebookKernelService, - private readonly _editorService: IEditorService, - private readonly _productService: IProductService, - private readonly _quickInputService: IQuickInputService, - private readonly _labelService: ILabelService, - private readonly _logService: ILogService, - private readonly _paneCompositePartService: IPaneCompositePartService, - private readonly _extensionWorkbenchService: IExtensionsWorkbenchService, - private readonly _extensionService: IExtensionService, - ) { } - async showQuickPick(context?: KernelQuickPickContext): Promise { - const editor = this._getEditorFromContext(context); - - if (!editor || !editor.hasModel()) { - return false; - } - let controllerId = context && 'id' in context ? context.id : undefined; - let extensionId = context && 'extension' in context ? context.extension : undefined; - - if (controllerId && (typeof controllerId !== 'string' || typeof extensionId !== 'string')) { - // validate context: id & extension MUST be strings - controllerId = undefined; - extensionId = undefined; - } - - const notebook = editor.textModel; - const scopedContextKeyService = editor.scopedContextKeyService; - const matchResult = this._notebookKernelService.getMatchingKernel(notebook); - const { selected, all } = matchResult; - - if (selected && controllerId && selected.id === controllerId && ExtensionIdentifier.equals(selected.extension, extensionId)) { - // current kernel is wanted kernel -> done - return true; - } - - let newKernel: INotebookKernel | undefined; - if (controllerId) { - const wantedId = `${extensionId}/${controllerId}`; - for (const candidate of all) { - if (candidate.id === wantedId) { - newKernel = candidate; - break; - } - } - if (!newKernel) { - this._logService.warn(`wanted kernel DOES NOT EXIST, wanted: ${wantedId}, all: ${all.map(k => k.id)}`); - return false; - } - } - - if (newKernel) { - this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); - return true; - } - - const quickPick = this._quickInputService.createQuickPick(); - const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); - quickPick.items = quickPickItems; - quickPick.canSelectMany = false; - quickPick.placeholder = selected - ? localize('prompt.placeholder.change', "Change kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true })) - : localize('prompt.placeholder.select', "Select kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true })); - - quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0; - - const kernelDetectionTaskListener = this._notebookKernelService.onDidChangeKernelDetectionTasks(() => { - quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0; - }); - - // run extension recommendataion task if quickPickItems is empty - const extensionRecommendataionPromise = quickPickItems.length === 0 - ? createCancelablePromise(token => this._showInstallKernelExtensionRecommendation(notebook, quickPick, this._extensionWorkbenchService, token)) - : undefined; - - const kernelChangeEventListener = Event.debounce( - Event.any( - this._notebookKernelService.onDidChangeSourceActions, - this._notebookKernelService.onDidAddKernel, - this._notebookKernelService.onDidRemoveKernel, - this._notebookKernelService.onDidChangeNotebookAffinity - ), - (last, _current) => last, - KERNEL_PICKER_UPDATE_DEBOUNCE - )(async () => { - // reset quick pick progress - quickPick.busy = false; - extensionRecommendataionPromise?.cancel(); - - const currentActiveItems = quickPick.activeItems; - const matchResult = this._notebookKernelService.getMatchingKernel(notebook); - const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); - quickPick.keepScrollPosition = true; - - // recalcuate active items - const activeItems: KernelQuickPickItem[] = []; - for (const item of currentActiveItems) { - if (isKernelPick(item)) { - const kernelId = item.kernel.id; - const sameItem = quickPickItems.find(pi => isKernelPick(pi) && pi.kernel.id === kernelId) as KernelPick | undefined; - if (sameItem) { - activeItems.push(sameItem); - } - } else if (isSourcePick(item)) { - const sameItem = quickPickItems.find(pi => isSourcePick(pi) && pi.action.action.id === item.action.action.id) as SourcePick | undefined; - if (sameItem) { - activeItems.push(sameItem); - } - } - } - - quickPick.items = quickPickItems; - quickPick.activeItems = activeItems; - }, this); - - const pick = await new Promise((resolve, reject) => { - quickPick.onDidAccept(() => { - const item = quickPick.selectedItems[0]; - if (item) { - resolve(item); - } else { - reject(); - } - - quickPick.hide(); - }); - - quickPick.onDidHide(() => () => { - kernelDetectionTaskListener.dispose(); - kernelChangeEventListener.dispose(); - quickPick.dispose(); - reject(); - }); - quickPick.show(); - }); - - if (pick) { - if (isKernelPick(pick)) { - newKernel = pick.kernel; - this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); - return true; - } - - // actions - if (pick.id === 'install') { - await this._showKernelExtension( - this._paneCompositePartService, - this._extensionWorkbenchService, - this._extensionService, - notebook.viewType - ); - // suggestedExtension must be defined for this option to be shown, but still check to make TS happy - } else if (isInstallExtensionPick(pick)) { - await this._showKernelExtension( - this._paneCompositePartService, - this._extensionWorkbenchService, - this._extensionService, - notebook.viewType, - pick.extensionId, - this._productService.quality !== 'stable' - ); - } else if (isSourcePick(pick)) { - // selected explicilty, it should trigger the execution? - pick.action.runAction(); - } - } - - return false; - } - - private _getEditorFromContext(context?: KernelQuickPickContext): INotebookEditor | undefined { - let editor: INotebookEditor | undefined; - if (context !== undefined && 'notebookEditorId' in context) { - const editorId = context.notebookEditorId; - const matchingEditor = this._editorService.visibleEditorPanes.find((editorPane) => { - const notebookEditor = getNotebookEditorFromEditorPane(editorPane); - return notebookEditor?.getId() === editorId; - }); - editor = getNotebookEditorFromEditorPane(matchingEditor); - } else if (context !== undefined && 'notebookEditor' in context) { - editor = context?.notebookEditor; - } else { - editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - } - - return editor; - } - - private _getKernelPickerQuickPickItems( - notebookTextModel: NotebookTextModel, - matchResult: INotebookKernelMatchResult, - notebookKernelService: INotebookKernelService, - scopedContextKeyService: IContextKeyService - ): QuickPickInput[] { - const { selected, all, suggestions, hidden } = matchResult; - - const quickPickItems: QuickPickInput[] = []; - if (all.length) { - // Always display suggested kernels on the top. - this._fillInSuggestions(quickPickItems, suggestions, selected); - - // Next display all of the kernels not marked as hidden grouped by categories or extensions. - // If we don't have a kind, always display those at the bottom. - const picks = all.filter(item => (!suggestions.includes(item) && !hidden.includes(item))).map(kernel => this._toQuickPick(kernel, selected)); - const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); - kernelsPerCategory.forEach(items => { - quickPickItems.push({ - type: 'separator', - label: items[0].kernel.kind || localize('otherKernelKinds', "Other") - }); - quickPickItems.push(...items); - }); - } - - const sourceActions = notebookKernelService.getSourceActions(notebookTextModel, scopedContextKeyService); - if (sourceActions.length) { - quickPickItems.push({ - type: 'separator', - // label: localize('sourceActions', "") - }); - - sourceActions.forEach(sourceAction => { - const res = { - action: sourceAction, - picked: false, - label: sourceAction.action.label, - }; - - quickPickItems.push(res); - }); - } - - return quickPickItems; - } - - private _toQuickPick(kernel: INotebookKernel, selected: INotebookKernel | undefined) { - const res = { - kernel, - picked: kernel.id === selected?.id, - label: kernel.label, - description: kernel.description, - detail: kernel.detail - }; - if (kernel.id === selected?.id) { - if (!res.description) { - res.description = localize('current1', "Currently Selected"); - } else { - res.description = localize('current2', "{0} - Currently Selected", res.description); - } - } - return res; - } - - private _fillInSuggestions(quickPickItems: QuickPickInput[], suggestions: INotebookKernel[], selected: INotebookKernel | undefined) { - if (!suggestions.length) { - return; - } - - if (suggestions.length === 1 && suggestions[0].id === selected?.id) { - quickPickItems.push({ - type: 'separator', - label: localize('selectedKernels', "Selected") - }); - - // The title is already set to "Selected" so we don't need to set it again in description, thus passing in `undefined`. - quickPickItems.push(this._toQuickPick(suggestions[0], undefined)); - return; - } - - quickPickItems.push({ - type: 'separator', - label: localize('suggestedKernels', "Suggested") - }); - quickPickItems.push(...suggestions.map(kernel => this._toQuickPick(kernel, selected))); - } - - private async _showInstallKernelExtensionRecommendation( - notebookTextModel: NotebookTextModel, - quickPick: IQuickPick, - extensionWorkbenchService: IExtensionsWorkbenchService, - token: CancellationToken - ) { - quickPick.busy = true; - - const newQuickPickItems = await this._getKernelRecommendationsQuickPickItems(notebookTextModel, extensionWorkbenchService); - quickPick.busy = false; - - if (token.isCancellationRequested) { - return; - } - - if (newQuickPickItems && quickPick.items.length === 0) { - quickPick.items = newQuickPickItems; - } - } - - private async _getKernelRecommendationsQuickPickItems( - notebookTextModel: NotebookTextModel, - extensionWorkbenchService: IExtensionsWorkbenchService, - ): Promise[] | undefined> { - const quickPickItems: QuickPickInput[] = []; - - const language = this.getSuggestedLanguage(notebookTextModel); - const suggestedExtension: INotebookExtensionRecommendation | undefined = language ? this.getSuggestedKernelFromLanguage(notebookTextModel.viewType, language) : undefined; - if (suggestedExtension) { - await extensionWorkbenchService.queryLocal(); - const extension = extensionWorkbenchService.installed.find(e => e.identifier.id === suggestedExtension.extensionId); - - if (extension) { - // it's installed but might be detecting kernels - return undefined; - } - - // We have a suggested kernel, show an option to install it - quickPickItems.push({ - id: 'installSuggested', - description: suggestedExtension.displayName ?? suggestedExtension.extensionId, - label: `$(${Codicon.lightbulb.id}) ` + localize('installSuggestedKernel', 'Install suggested extensions'), - extensionId: suggestedExtension.extensionId - }); - } - // there is no kernel, show the install from marketplace - quickPickItems.push({ - id: 'install', - label: localize('searchForKernels', "Browse marketplace for kernel extensions"), - }); - - return quickPickItems; - } - - /** - * Examine the most common language in the notebook - * @param notebookTextModel The notebook text model - * @returns What the suggested language is for the notebook. Used for kernal installing - */ - private getSuggestedLanguage(notebookTextModel: NotebookTextModel): string | undefined { - const metaData = notebookTextModel.metadata; - let suggestedKernelLanguage: string | undefined = (metaData.custom as any)?.metadata?.language_info?.name; - // TODO how do we suggest multi language notebooks? - if (!suggestedKernelLanguage) { - const cellLanguages = notebookTextModel.cells.map(cell => cell.language).filter(language => language !== 'markdown'); - // Check if cell languages is all the same - if (cellLanguages.length > 1) { - const firstLanguage = cellLanguages[0]; - if (cellLanguages.every(language => language === firstLanguage)) { - suggestedKernelLanguage = firstLanguage; - } - } - } - return suggestedKernelLanguage; - } - - /** - * Given a language and notebook view type suggest a kernel for installation - * @param language The language to find a suggested kernel extension for - * @returns A recommednation object for the recommended extension, else undefined - */ - private getSuggestedKernelFromLanguage(viewType: string, language: string): INotebookExtensionRecommendation | undefined { - const recommendation = KERNEL_RECOMMENDATIONS.get(viewType)?.get(language); - return recommendation; - } - - private async _showKernelExtension( - paneCompositePartService: IPaneCompositePartService, - extensionWorkbenchService: IExtensionsWorkbenchService, - extensionService: IExtensionService, - viewType: string, - extId?: string, - isInsiders?: boolean - ) { - // If extension id is provided attempt to install the extension as the user has requested the suggested ones be installed - if (extId) { - const extension = (await extensionWorkbenchService.getExtensions([{ id: extId }], CancellationToken.None))[0]; - const canInstall = await extensionWorkbenchService.canInstall(extension); - // If we can install then install it, otherwise we will fall out into searching the viewlet - if (canInstall) { - await extensionWorkbenchService.install( - extension, - { - installPreReleaseVersion: isInsiders ?? false, - context: { skipWalkthrough: true } - }, - ProgressLocation.Notification - ); - await extensionService.activateByEvent(`onNotebook:${viewType}`); - return; - } - } - - const viewlet = await paneCompositePartService.openPaneComposite(EXTENSION_VIEWLET_ID, ViewContainerLocation.Sidebar, true); - const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; - const pascalCased = viewType.split(/[^a-z0-9]/ig).map(uppercaseFirstLetter).join(''); - view?.search(`@tag:notebookKernel${pascalCased}`); - } -} registerAction2(class extends Action2 { constructor() { @@ -513,28 +77,18 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor, context?: KernelQuickPickContext): Promise { - const notebookKernelService = accessor.get(INotebookKernelService); - const editorService = accessor.get(IEditorService); - const productService = accessor.get(IProductService); - const quickInputService = accessor.get(IQuickInputService); - const labelService = accessor.get(ILabelService); - const logService = accessor.get(ILogService); - const paneCompositeService = accessor.get(IPaneCompositePartService); - const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); - const extensionHostService = accessor.get(IExtensionService); + const instantiationService = accessor.get(IInstantiationService); + const configurationService = accessor.get(IConfigurationService); - const strategy = new KernelPickerFlatStrategy( - notebookKernelService, - editorService, - productService, - quickInputService, - labelService, - logService, - paneCompositeService, - extensionWorkbenchService, - extensionHostService - ); - return await strategy.showQuickPick(context); + const usingMru = configurationService.getValue('notebook.experimental.kernelPicker.mru'); + + if (usingMru) { + const strategy = instantiationService.createInstance(KernelPickerMRUStrategy); + return await strategy.showQuickPick(context); + } else { + const strategy = instantiationService.createInstance(KernelPickerFlatStrategy); + return await strategy.showQuickPick(context); + } } }); @@ -546,6 +100,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem { actualAction: IAction, private readonly _editor: { onDidChangeModel: Event; textModel: NotebookTextModel | undefined; scopedContextKeyService?: IContextKeyService } | INotebookEditor, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super( undefined, @@ -584,76 +139,14 @@ export class NotebooKernelActionViewItem extends ActionViewItem { return; } - const detectionTasks = this._notebookKernelService.getKernelDetectionTasks(notebook); - if (detectionTasks.length) { - return this._updateActionFromDetectionTask(); + const usingMru = this._configurationService.getValue('notebook.experimental.kernelPicker.mru'); + if (usingMru) { + KernelPickerMRUStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService); + } else { + KernelPickerFlatStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService, this._editor.scopedContextKeyService); } - const runningActions = this._notebookKernelService.getRunningSourceActions(notebook); - if (runningActions.length) { - return this._updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true); - } - - const info = this._notebookKernelService.getMatchingKernel(notebook); - if (info.all.length === 0) { - return this._updateActionsFromSourceActions(); - } - - this._updateActionFromKernelInfo(info); - } - - private _updateActionFromDetectionTask() { - this._action.enabled = true; - this._action.label = localize('kernels.detecting', "Detecting Kernels"); - this._action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')); - } - - private _updateActionFromSourceAction(sourceAction: ISourceAction, running: boolean) { - const action = sourceAction.action; - this.action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon); this.updateClass(); - this._action.label = action.label; - this._action.enabled = true; - } - - private _updateActionsFromSourceActions() { - this._action.enabled = true; - const sourceActions = this._editor.textModel ? this._notebookKernelService.getSourceActions(this._editor.textModel, this._editor.scopedContextKeyService) : []; - if (sourceActions.length === 1) { - // exact one action - this._updateActionFromSourceAction(sourceActions[0], false); - } else if (sourceActions.filter(sourceAction => sourceAction.isPrimary).length === 1) { - // exact one primary action - this._updateActionFromSourceAction(sourceActions.filter(sourceAction => sourceAction.isPrimary)[0], false); - } else { - this._action.class = ThemeIcon.asClassName(selectKernelIcon); - this._action.label = localize('select', "Select Kernel"); - this._action.tooltip = ''; - } - } - - private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { - this._action.enabled = true; - this._action.class = ThemeIcon.asClassName(selectKernelIcon); - const selectedOrSuggested = info.selected - ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined) - ?? (info.all.length === 1 ? info.all[0] : undefined); - if (selectedOrSuggested) { - // selected or suggested kernel - this._action.label = this._generateKenrelLabel(selectedOrSuggested); - this._action.tooltip = selectedOrSuggested.description ?? selectedOrSuggested.detail ?? ''; - if (!info.selected) { - // special UI for selected kernel? - } - } else { - // many kernels or no kernels - this._action.label = localize('select', "Select Kernel"); - this._action.tooltip = ''; - } - } - - private _generateKenrelLabel(kernel: INotebookKernel) { - return kernel.label; } private _resetAction(): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index c033b37ceb8..a4d954b227b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -1051,3 +1051,10 @@ function formatStreamText(buffer: VSBuffer): VSBuffer { // Do the same thing jupyter is doing return VSBuffer.fromString(fixCarriageReturn(fixBackspace(textDecoder.decode(buffer.buffer)))); } + +export interface INotebookKernelSourceAction { + readonly label: string; + readonly description?: string; + readonly detail?: string; + readonly command?: string | Command; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index a79f5505650..86493335977 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -10,6 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookKernelSourceAction } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export interface ISelectedNotebooksChangeEvent { notebook: URI; @@ -83,6 +84,11 @@ export interface INotebookSourceActionChangeEvent { notebook: URI; } +export interface IKernelSourceActionProvider { + readonly viewType: string; + provideKernelSourceActions(): Promise; +} + export interface INotebookTextModelLike { uri: URI; viewType: string } export const INotebookKernelService = createDecorator('INotebookKernelService'); @@ -129,5 +135,7 @@ export interface INotebookKernelService { readonly onDidChangeSourceActions: Event; getSourceActions(notebook: INotebookTextModelLike, contextKeyService: IContextKeyService | undefined): ISourceAction[]; getRunningSourceActions(notebook: INotebookTextModelLike): ISourceAction[]; + registerKernelSourceActionProvider(viewType: string, provider: IKernelSourceActionProvider): IDisposable; + getKernelSourceActions2(notebook: INotebookTextModelLike): Promise; //#endregion } diff --git a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts index 40d0d7de1b5..6820da8577b 100644 --- a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts @@ -11,10 +11,31 @@ declare module 'vscode' { dispose(): void; } + export class NotebookKernelSourceAction { + readonly label: string; + readonly description?: string; + readonly detail?: string; + readonly command: string | Command; + + constructor(label: string); + } + + export interface NotebookKernelSourceActionProvider { + /** + * Provide kernel source actions + */ + provideNotebookKernelSourceActions(token: CancellationToken): ProviderResult; + } + export namespace notebooks { /** * Create notebook controller detection task */ export function createNotebookControllerDetectionTask(notebookType: string): NotebookControllerDetectionTask; + + /** + * Register a notebook kernel source action provider + */ + export function registerKernelSourceActionProvider(notebookType: string, provider: NotebookKernelSourceActionProvider): Disposable; } } From 126fd696daccd32c2abc974a4fb47423e4cb3885 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 17 Nov 2022 00:57:31 +0100 Subject: [PATCH 23/67] implement import/export profiles (#166517) * implement import/export profiles - include all resources - include all user profile storage - show import/export preview view * fix compilation --- .../common/inMemoryFilesystemProvider.ts | 18 +- .../userDataProfileStorageService.ts | 3 + .../parts/activitybar/activitybarPart.ts | 10 - .../browser/parts/panel/panelPart.ts | 7 - .../browser/parts/statusbar/statusbarModel.ts | 8 - .../test/browser/fileEditorInput.test.ts | 9 +- .../browser/userDataProfile.ts | 57 +- .../nativeExtensionManagementService.ts | 13 +- .../browser/extensionsResource.ts | 224 ++++++ .../browser/globalStateResource.ts | 116 +++ .../browser/keybindingsResource.ts | 93 +++ .../browser/settingsResource.ts | 112 +++ .../browser/snippetsResource.ts | 131 ++++ .../userDataProfile/browser/tasksResource.ts | 91 +++ .../userDataProfileImportExportService.ts | 699 ++++++++++++++++++ .../common/extensionsResource.ts | 102 --- .../common/globalStateResource.ts | 64 -- .../common/settingsResource.ts | 66 -- .../userDataProfile/common/userDataProfile.ts | 36 +- .../userDataProfileImportExportService.ts | 96 --- .../common/userDataProfileService.ts | 7 +- .../common/userDataProfileStorageRegistry.ts | 62 -- .../views/browser/viewDescriptorService.ts | 6 - .../views/common/viewContainerModel.ts | 6 - .../test/browser/workbenchTestServices.ts | 9 +- src/vs/workbench/workbench.common.main.ts | 2 +- src/vs/workbench/workbench.desktop.main.ts | 1 + 27 files changed, 1561 insertions(+), 487 deletions(-) create mode 100644 src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts create mode 100644 src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts create mode 100644 src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts create mode 100644 src/vs/workbench/services/userDataProfile/browser/settingsResource.ts create mode 100644 src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts create mode 100644 src/vs/workbench/services/userDataProfile/browser/tasksResource.ts create mode 100644 src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts delete mode 100644 src/vs/workbench/services/userDataProfile/common/extensionsResource.ts delete mode 100644 src/vs/workbench/services/userDataProfile/common/globalStateResource.ts delete mode 100644 src/vs/workbench/services/userDataProfile/common/settingsResource.ts delete mode 100644 src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts delete mode 100644 src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts diff --git a/src/vs/platform/files/common/inMemoryFilesystemProvider.ts b/src/vs/platform/files/common/inMemoryFilesystemProvider.ts index 5ee8e5366b6..615948123ad 100644 --- a/src/vs/platform/files/common/inMemoryFilesystemProvider.ts +++ b/src/vs/platform/files/common/inMemoryFilesystemProvider.ts @@ -52,10 +52,20 @@ export type Entry = File | Directory; export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { - readonly capabilities: FileSystemProviderCapabilities = - FileSystemProviderCapabilities.FileReadWrite - | FileSystemProviderCapabilities.PathCaseSensitive; - readonly onDidChangeCapabilities: Event = Event.None; + private _onDidChangeCapabilities = this._register(new Emitter()); + readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event; + + private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive; + get capabilities(): FileSystemProviderCapabilities { return this._capabilities; } + + setReadOnly(readonly: boolean) { + const isReadonly = !!(this._capabilities & FileSystemProviderCapabilities.Readonly); + if (readonly !== isReadonly) { + this._capabilities = readonly ? FileSystemProviderCapabilities.Readonly | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadWrite + : FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive; + this._onDidChangeCapabilities.fire(); + } + } root = new Directory(''); diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts index b0d9c5082fd..f000b71599d 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts @@ -12,6 +12,7 @@ import { AbstractUserDataProfileStorageService, IProfileStorageChanges, IUserDat import { isProfileUsingDefaultStorage, IStorageService } from 'vs/platform/storage/common/storage'; import { ApplicationStorageDatabaseClient, ProfileStorageDatabaseClient } from 'vs/platform/storage/common/storageIpc'; import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class UserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService { @@ -50,3 +51,5 @@ export class UserDataProfileStorageService extends AbstractUserDataProfileStorag return isProfileUsingDefaultStorage(profile) ? new ApplicationStorageDatabaseClient(storageChannel) : new ProfileStorageDatabaseClient(storageChannel, profile); } } + +registerSingleton(IUserDataProfileStorageService, UserDataProfileStorageService, InstantiationType.Delayed); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index f1c508f2e79..2efbf6f249b 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -43,8 +43,6 @@ import { StringSHA1 } from 'vs/base/common/hash'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { GestureEvent } from 'vs/base/browser/touch'; import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; import { IUserDataProfileService, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -149,14 +147,6 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart this.registerListeners(); - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: ActivitybarPart.PINNED_VIEW_CONTAINERS, - description: localize('pinned view containers', "Activity bar entries visibility customizations") - }, { - key: AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, - description: localize('accounts visibility key', "Accounts entry visibility customization in the activity bar.") - }]); } private createCompositeBar() { diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 513e653171a..9fbb998f5f3 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -44,7 +44,6 @@ import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/bro import { IPartOptions } from 'vs/workbench/browser/part'; import { StringSHA1 } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -210,12 +209,6 @@ export abstract class BasePanelPart extends CompositePart impleme // Global Panel Actions this.globalActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, partId === Parts.PANEL_PART ? MenuId.PanelTitle : MenuId.AuxiliaryBarTitle, undefined, undefined)); this._register(this.globalActions.onDidChange(() => this.updateGlobalToolbarActions())); - - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: this.pinnedPanelsKey, - description: localize('pinned view containers', "Panel entries visibility customizations") - }]); } protected abstract getActivityHoverOptions(): IActivityHoverOptions; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts b/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts index 1f5f723f8f0..bf44fea999d 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts @@ -8,9 +8,6 @@ import { isStatusbarEntryLocation, IStatusbarEntryLocation, StatusbarAlignment } import { hide, show, isAncestor } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { Emitter } from 'vs/base/common/event'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; -import { localize } from 'vs/nls'; export interface IStatusbarEntryPriority { @@ -68,11 +65,6 @@ export class StatusbarViewModel extends Disposable { this.restoreState(); this.registerListeners(); - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: StatusbarViewModel.HIDDEN_ENTRIES_KEY, - description: localize('statusbar.hidden', "Status bar entries visibility customizations"), - }]); } private restoreState(): void { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 86fb1f94e9b..6b83ad251b3 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -11,7 +11,7 @@ import { workbenchInstantiationService, TestServiceAccessor, getLastResolvedFile import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorFactoryRegistry, Verbosity, EditorExtensions, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EncodingMode, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { timeout } from 'vs/base/common/async'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; @@ -127,11 +127,10 @@ suite('Files - FileEditorInput', () => { test('reports as readonly with readonly file scheme', async function () { - class ReadonlyInMemoryFileSystemProvider extends InMemoryFileSystemProvider { - override readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly; - } + const inMemoryFilesystemProvider = new InMemoryFileSystemProvider(); + inMemoryFilesystemProvider.setReadOnly(true); - const disposable = accessor.fileService.registerProvider('someTestingReadonlyScheme', new ReadonlyInMemoryFileSystemProvider()); + const disposable = accessor.fileService.registerProvider('someTestingReadonlyScheme', inMemoryFilesystemProvider); try { const input = createFileInput(toResource.call(this, '/foo/bar/file.js').with({ scheme: 'someTestingReadonlyScheme' })); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 546d3277dd6..d5b8b668319 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -19,18 +19,17 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, isUserDataProfileTemplate, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, IUserDataProfileTemplate, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE, PROFILE_EXTENSION, PROFILE_FILTER } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, isUserDataProfileTemplate, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, IUserDataProfileTemplate, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE, PROFILE_FILTER } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { charCount } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { joinPath } from 'vs/base/common/resources'; import { Codicon } from 'vs/base/common/codicons'; import { IFileService } from 'vs/platform/files/common/files'; import { asJson, asText, IRequestService } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -262,6 +261,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements original: `Export (${that.userDataProfileService.currentProfile.name})...` }, category: PROFILES_CATEGORY, + precondition: IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.toNegated(), menu: [ { id: ManageProfilesSubMenu, @@ -276,25 +276,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } async run(accessor: ServicesAccessor) { - const textFileService = accessor.get(ITextFileService); - const fileDialogService = accessor.get(IFileDialogService); const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); - const notificationService = accessor.get(INotificationService); - - const profileLocation = await fileDialogService.showSaveDialog({ - title: localize('export profile dialog', "Save Profile"), - filters: PROFILE_FILTER, - defaultUri: joinPath(await fileDialogService.defaultFilePath(), `profile.${PROFILE_EXTENSION}`), - }); - - if (!profileLocation) { - return; - } - - const profile = await userDataProfileImportExportService.exportProfile({ skipComments: true }); - await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]); - - notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY.value)); + return userDataProfileImportExportService.exportProfile(); } })); disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, { @@ -323,6 +306,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }, category: PROFILES_CATEGORY, f1: true, + precondition: IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.toNegated(), menu: [ { id: ManageProfilesSubMenu, @@ -358,11 +342,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const disposables = new DisposableStore(); const quickPick = disposables.add(quickInputService.createQuickPick()); const updateQuickPickItems = (value?: string) => { - const selectFromFileItem: IQuickPickItem = { label: isSettingProfilesEnabled ? localize('select from file', "Select Profile template file") : localize('import from file', "Import from profile file") }; - quickPick.items = value ? [{ label: isSettingProfilesEnabled ? localize('select from url', "Create from template URL") : localize('import from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem]; + const selectFromFileItem: IQuickPickItem = { label: localize('import from file', "Import from profile file") }; + quickPick.items = value ? [{ label: localize('import from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem]; }; - quickPick.title = isSettingProfilesEnabled ? localize('create from profile template quick pick title', "Create from Profile Template") : localize('import profile quick pick title', "Import Settings from a Profile"); - quickPick.placeholder = isSettingProfilesEnabled ? localize('create from profile template placeholder', "Provide a template URL or Select a template file") : localize('import profile placeholder', "Provide profile URL or select profile file to import"); + quickPick.title = localize('import profile quick pick title', "Import Profile"); + quickPick.placeholder = localize('import profile placeholder', "Provide profile URL or select profile file to import"); quickPick.ignoreFocusOut = true; disposables.add(quickPick.onDidChangeValue(updateQuickPickItems)); updateQuickPickItems(); @@ -371,11 +355,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements disposables.add(quickPick.onDidAccept(async () => { try { quickPick.hide(); - const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); - if (profile) { - if (isSettingProfilesEnabled) { + if (isSettingProfilesEnabled) { + const profile = quickPick.selectedItems[0].description ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService); + if (profile) { await userDataProfileImportExportService.importProfile(profile); - } else { + } + } else { + const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); + if (profile) { await userDataProfileImportExportService.setProfile(profile); } } @@ -387,7 +374,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.show(); } - private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise { + private async getProfileUriFromFileSystem(fileDialogService: IFileDialogService): Promise { const profileLocation = await fileDialogService.showOpenDialog({ canSelectFolders: false, canSelectFiles: true, @@ -398,7 +385,15 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements if (!profileLocation) { return null; } - const content = (await fileService.readFile(profileLocation[0])).value.toString(); + return profileLocation[0]; + } + + private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise { + const profileLocation = await this.getProfileUriFromFileSystem(fileDialogService); + if (!profileLocation) { + return null; + } + const content = (await fileService.readFile(profileLocation)).value.toString(); const parsed = JSON.parse(content); return isUserDataProfileTemplate(parsed) ? parsed : null; } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts index 6d2bf5f714b..5485b67400b 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts @@ -68,22 +68,25 @@ export class NativeExtensionManagementService extends ExtensionManagementChannel override async install(vsix: URI, options?: InstallVSIXOptions): Promise { const { location, cleanup } = await this.downloadVsix(vsix); try { - return await super.install(location, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); + 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 { - return super.installFromGallery(extension, { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); + installOptions = installOptions?.profileLocation ? installOptions : { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; + return super.installFromGallery(extension, installOptions); } override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - return super.uninstall(extension, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); + options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; + return super.uninstall(extension, options); } - override getInstalled(type: ExtensionType | null = null): Promise { - return super.getInstalled(type, this.userDataProfileService.currentProfile.extensionsResource); + override getInstalled(type: ExtensionType | null = null, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { + return super.getInstalled(type, profileLocation); } private async downloadVsix(vsix: URI): Promise<{ location: URI; cleanup: () => Promise }> { diff --git a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts new file mode 100644 index 00000000000..81e9272808f --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts @@ -0,0 +1,224 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface IProfileExtension { + identifier: IExtensionIdentifier; + displayName?: string; + preRelease?: boolean; + disabled?: boolean; +} + +export class ExtensionsResource implements IProfileResource { + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile, exclude?: string[]): Promise { + const extensions = await this.getLocalExtensions(profile); + return JSON.stringify(exclude?.length ? extensions.filter(e => !exclude.includes(e.identifier.id.toLowerCase())) : extensions); + } + + async apply(content: string, profile: IUserDataProfile): Promise { + return this.withProfileScopedServices(profile, async (extensionEnablementService) => { + const profileExtensions: IProfileExtension[] = await this.getProfileExtensions(content); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource); + const extensionsToEnableOrDisable: { extension: ILocalExtension; enable: boolean }[] = []; + const extensionsToInstall: IProfileExtension[] = []; + for (const e of profileExtensions) { + const isDisabled = extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); + const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier)); + if (!installedExtension || installedExtension.preRelease !== e.preRelease) { + extensionsToInstall.push(e); + } + if (installedExtension && isDisabled !== !!e.disabled) { + extensionsToEnableOrDisable.push({ extension: installedExtension, enable: !!e.disabled }); + } + } + const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => extension.type === ExtensionType.User && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); + for (const { extension, enable } of extensionsToEnableOrDisable) { + if (enable) { + await extensionEnablementService.enableExtension(extension.identifier); + } else { + await extensionEnablementService.disableExtension(extension.identifier); + } + } + if (extensionsToInstall.length) { + const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, hasPreRelease: e.preRelease })), CancellationToken.None); + await Promise.all(extensionsToInstall.map(async e => { + const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier)); + if (!extension) { + return; + } + if (await this.extensionManagementService.canInstall(extension)) { + await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease, profileLocation: profile.extensionsResource } /* set isMachineScoped value to prevent install and sync dialog in web */); + } else { + this.logService.info(`Profile: Skipped installing extension because it cannot be installed.`, extension.displayName || extension.identifier.id); + } + })); + } + if (extensionsToUninstall.length) { + await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e))); + } + }); + } + + async getLocalExtensions(profile: IUserDataProfile): Promise { + return this.withProfileScopedServices(profile, async (extensionEnablementService) => { + const result: Array = []; + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource); + const disabledExtensions = extensionEnablementService.getDisabledExtensions(); + for (const extension of installedExtensions) { + const { identifier, preRelease } = extension; + const disabled = disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)); + if (extension.type === ExtensionType.System && !disabled) { + // skip enabled system extensions + continue; + } + if (extension.type === ExtensionType.User) { + if (!extension.identifier.uuid) { + // skip user extensions without uuid + continue; + } + if (disabled && !extension.isBuiltin) { + // skip user disabled extensions + continue; + } + } + const profileExtension: IProfileExtension = { identifier, displayName: extension.manifest.displayName }; + if (disabled) { + profileExtension.disabled = true; + } + if (preRelease) { + profileExtension.preRelease = true; + } + result.push(profileExtension); + } + return result; + }); + } + + async getProfileExtensions(content: string): Promise { + return JSON.parse(content); + } + + private async withProfileScopedServices(profile: IUserDataProfile, fn: (extensionEnablementService: IGlobalExtensionEnablementService) => Promise): Promise { + return this.userDataProfileStorageService.withProfileScopedStorageService(profile, + async storageService => { + const disposables = new DisposableStore(); + const instantiationService = this.instantiationService.createChild(new ServiceCollection([IStorageService, storageService])); + const extensionEnablementService = disposables.add(instantiationService.createInstance(GlobalExtensionEnablementService)); + try { + return await fn(extensionEnablementService); + } finally { + disposables.dispose(); + } + }); + } +} + +export class ExtensionsResourceExportTreeItem implements IProfileResourceTreeItem { + + readonly handle = this.profile.extensionsResource.toString(); + readonly label = { label: localize('extensions', "Extensions") }; + readonly collapsibleState = TreeItemCollapsibleState.Expanded; + checkbox: ITreeItemCheckboxState = { isChecked: true }; + + private readonly excludedExtensions = new Set(); + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + async getChildren(): Promise { + const extensions = await this.instantiationService.createInstance(ExtensionsResource).getLocalExtensions(this.profile); + const that = this; + return extensions.map(e => ({ + handle: e.identifier.id.toLowerCase(), + parent: this, + label: { label: e.displayName || e.identifier.id }, + description: e.disabled ? localize('disabled', "Disabled") : undefined, + collapsibleState: TreeItemCollapsibleState.None, + checkbox: { + get isChecked() { return !that.excludedExtensions.has(e.identifier.id.toLowerCase()); }, + set isChecked(value: boolean) { + if (value) { + that.excludedExtensions.delete(e.identifier.id.toLowerCase()); + } else { + that.excludedExtensions.add(e.identifier.id.toLowerCase()); + } + } + }, + command: { + id: 'extension.open', + title: '', + arguments: [e.identifier.id] + } + })); + } + + async hasContent(): Promise { + const extensions = await this.instantiationService.createInstance(ExtensionsResource).getLocalExtensions(this.profile); + return extensions.length > 0; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(ExtensionsResource).getContent(this.profile, [...this.excludedExtensions.values()]); + } + +} + +export class ExtensionsResourceImportTreeItem implements IProfileResourceTreeItem { + + readonly handle = 'extensions'; + readonly label = { label: localize('extensions', "Extensions") }; + readonly collapsibleState = TreeItemCollapsibleState.Expanded; + + constructor( + private readonly content: string, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + async getChildren(): Promise { + const extensions = await this.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(this.content); + return extensions.map(e => ({ + handle: e.identifier.id.toLowerCase(), + parent: this, + label: { label: e.displayName || e.identifier.id }, + description: e.disabled ? localize('disabled', "Disabled") : undefined, + collapsibleState: TreeItemCollapsibleState.None, + command: { + id: 'extension.open', + title: '', + arguments: [e.identifier.id] + } + })); + } + +} + + diff --git a/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts new file mode 100644 index 00000000000..526a46ce468 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStringDictionary } from 'vs/base/common/collections'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface IGlobalState { + storage: IStringDictionary; +} + +export class GlobalStateResource implements IProfileResource { + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const globalState = await this.getGlobalState(profile); + return JSON.stringify(globalState); + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const globalState: IGlobalState = JSON.parse(content); + await this.writeGlobalState(globalState, profile); + } + + async getGlobalState(profile: IUserDataProfile): Promise { + const storage: IStringDictionary = {}; + const storageData = await this.userDataProfileStorageService.readStorageData(profile); + for (const [key, value] of storageData) { + if (value.value !== undefined && value.target === StorageTarget.USER) { + storage[key] = value.value; + } + } + return { storage }; + } + + private async writeGlobalState(globalState: IGlobalState, profile: IUserDataProfile): Promise { + const storageKeys = Object.keys(globalState.storage); + if (storageKeys.length) { + const updatedStorage = new Map(); + const nonProfileKeys = [ + // Do not include application scope user target keys because they also include default profile user target keys + ...this.storageService.keys(StorageScope.APPLICATION, StorageTarget.MACHINE), + ...this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.USER), + ...this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.MACHINE), + ]; + for (const key of storageKeys) { + if (nonProfileKeys.includes(key)) { + this.logService.info(`Profile: Ignoring global state key '${key}' because it is not a profile key.`); + } else { + updatedStorage.set(key, globalState.storage[key]); + } + } + await this.userDataProfileStorageService.updateStorageData(profile, updatedStorage, StorageTarget.USER); + } + } +} + +export class GlobalStateResourceExportTreeItem implements IProfileResourceTreeItem { + + readonly handle = this.profile.globalStorageHome.toString(); + readonly label = { label: localize('globalState', "UI State") }; + readonly collapsibleState = TreeItemCollapsibleState.None; + checkbox: ITreeItemCheckboxState = { isChecked: true }; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { return undefined; } + + async hasContent(): Promise { + const globalState = await this.instantiationService.createInstance(GlobalStateResource).getGlobalState(this.profile); + return Object.keys(globalState.storage).length > 0; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(GlobalStateResource).getContent(this.profile); + } + +} + +export class GlobalStateResourceImportTreeItem implements IProfileResourceTreeItem { + + readonly handle = 'globalState'; + readonly label = { label: localize('globalState', "UI State") }; + readonly collapsibleState = TreeItemCollapsibleState.None; + readonly command = { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.resource, undefined, undefined] + }; + + constructor(private readonly resource: URI) { } + + async getChildren(): Promise { return undefined; } +} + + + diff --git a/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts new file mode 100644 index 00000000000..a2707695411 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { platform, Platform } from 'vs/base/common/platform'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; + +interface IKeybindingsResourceContent { + platform: Platform; + keybindings: string | null; +} + +export class KeybindingsResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const keybindingsContent = await this.getKeybindingsResourceContent(profile); + return JSON.stringify(keybindingsContent); + } + + async getKeybindingsResourceContent(profile: IUserDataProfile): Promise { + const keybindings = await this.getKeybindingsContent(profile); + return { keybindings, platform }; + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const keybindingsContent: IKeybindingsResourceContent = JSON.parse(content); + if (keybindingsContent.keybindings === null) { + this.logService.info(`Profile: No keybindings to apply...`); + return; + } + await this.fileService.writeFile(profile.keybindingsResource, VSBuffer.fromString(keybindingsContent.keybindings)); + } + + private async getKeybindingsContent(profile: IUserDataProfile): Promise { + try { + const content = await this.fileService.readFile(profile.keybindingsResource); + return content.value.toString(); + } catch (error) { + // File not found + if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return null; + } else { + throw error; + } + } + } + +} + +export class KeybindingsResourceTreeItem implements IProfileResourceTreeItem { + + readonly handle = this.profile.keybindingsResource.toString(); + readonly label = { label: localize('keybindings', "Keyboard Shortcuts") }; + readonly collapsibleState = TreeItemCollapsibleState.None; + checkbox: ITreeItemCheckboxState | undefined = { isChecked: true }; + readonly command = { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.profile.keybindingsResource, undefined, undefined] + }; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { return undefined; } + + async hasContent(): Promise { + const keybindingsContent = await this.instantiationService.createInstance(KeybindingsResource).getKeybindingsResourceContent(this.profile); + return keybindingsContent.keybindings !== null; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(KeybindingsResource).getContent(this.profile); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts new file mode 100644 index 00000000000..cdb12f8f329 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; + +interface ISettingsContent { + settings: string | null; +} + +export class SettingsResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const settingsContent = await this.getSettingsContent(profile); + return JSON.stringify(settingsContent); + } + + async getSettingsContent(profile: IUserDataProfile): Promise { + const localContent = await this.getLocalFileContent(profile); + if (localContent === null) { + return { settings: null }; + } else { + const ignoredSettings = this.getIgnoredSettings(); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(profile.settingsResource); + const settings = updateIgnoredSettings(localContent || '{}', '{}', ignoredSettings, formattingOptions); + return { settings }; + } + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const settingsContent: ISettingsContent = JSON.parse(content); + if (settingsContent.settings === null) { + this.logService.info(`Profile: No settings to apply...`); + return; + } + const localSettingsContent = await this.getLocalFileContent(profile); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(profile.settingsResource); + const contentToUpdate = updateIgnoredSettings(settingsContent.settings, localSettingsContent || '{}', this.getIgnoredSettings(), formattingOptions); + await this.fileService.writeFile(profile.settingsResource, VSBuffer.fromString(contentToUpdate)); + } + + private getIgnoredSettings(): string[] { + const allSettings = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const ignoredSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.MACHINE || allSettings[key]?.scope === ConfigurationScope.MACHINE_OVERRIDABLE); + return ignoredSettings; + } + + private async getLocalFileContent(profile: IUserDataProfile): Promise { + try { + const content = await this.fileService.readFile(profile.settingsResource); + return content.value.toString(); + } catch (error) { + // File not found + if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return null; + } else { + throw error; + } + } + } + +} + +export class SettingsResourceTreeItem implements IProfileResourceTreeItem { + + readonly handle = this.profile.settingsResource.toString(); + readonly label = { label: localize('settings', "Settings") }; + readonly collapsibleState = TreeItemCollapsibleState.None; + checkbox: ITreeItemCheckboxState | undefined = { isChecked: true }; + readonly command = { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.profile.settingsResource, undefined, undefined] + }; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { return undefined; } + + async hasContent(): Promise { + const settingsContent = await this.instantiationService.createInstance(SettingsResource).getSettingsContent(this.profile); + return settingsContent.settings !== null; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(SettingsResource).getContent(this.profile); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts new file mode 100644 index 00000000000..2cca57fa599 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { ResourceSet } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { FileOperationError, FileOperationResult, IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface ISnippetsContent { + snippets: IStringDictionary; +} + +export class SnippetsResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + } + + async getContent(profile: IUserDataProfile, excluded?: ResourceSet): Promise { + const snippets = await this.getSnippets(profile, excluded); + return JSON.stringify({ snippets }); + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const snippetsContent: ISnippetsContent = JSON.parse(content); + for (const key in snippetsContent.snippets) { + const resource = this.uriIdentityService.extUri.joinPath(profile.snippetsHome, key); + await this.fileService.writeFile(resource, VSBuffer.fromString(snippetsContent.snippets[key])); + } + } + + private async getSnippets(profile: IUserDataProfile, excluded?: ResourceSet): Promise> { + const snippets: IStringDictionary = {}; + const snippetsResources = await this.getSnippetsResources(profile, excluded); + for (const resource of snippetsResources) { + const key = this.uriIdentityService.extUri.relativePath(profile.snippetsHome, resource)!; + const content = await this.fileService.readFile(resource); + snippets[key] = content.value.toString(); + } + return snippets; + } + + async getSnippetsResources(profile: IUserDataProfile, excluded?: ResourceSet): Promise { + const snippets: URI[] = []; + let stat: IFileStat; + try { + stat = await this.fileService.resolve(profile.snippetsHome); + } catch (e) { + // No snippets + if (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return snippets; + } else { + throw e; + } + } + for (const { resource } of stat.children || []) { + if (excluded?.has(resource)) { + continue; + } + const extension = this.uriIdentityService.extUri.extname(resource); + if (extension === '.json' || extension === '.code-snippets') { + snippets.push(resource); + } + } + return snippets; + } +} + +export class SnippetsResourceTreeItem implements IProfileResourceTreeItem { + + readonly handle = this.profile.snippetsHome.toString(); + readonly label = { label: localize('snippets', "Snippets") }; + readonly collapsibleState = TreeItemCollapsibleState.Collapsed; + checkbox: ITreeItemCheckboxState | undefined = { isChecked: true }; + + private readonly excludedSnippets = new ResourceSet(); + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + async getChildren(): Promise { + const snippetsResources = await this.instantiationService.createInstance(SnippetsResource).getSnippetsResources(this.profile); + const that = this; + return snippetsResources.map(resource => ({ + handle: resource.toString(), + parent: that, + resourceUri: resource, + collapsibleState: TreeItemCollapsibleState.None, + checkbox: that.checkbox ? { + get isChecked() { return !that.excludedSnippets.has(resource); }, + set isChecked(value: boolean) { + if (value) { + that.excludedSnippets.delete(resource); + } else { + that.excludedSnippets.add(resource); + } + } + } : undefined, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [resource, undefined, undefined] + } + })); + } + + async hasContent(): Promise { + const snippetsResources = await this.instantiationService.createInstance(SnippetsResource).getSnippetsResources(this.profile); + return snippetsResources.length > 0; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(SnippetsResource).getContent(this.profile, this.excludedSnippets); + } + +} + diff --git a/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts new file mode 100644 index 00000000000..3929f989fc8 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { localize } from 'vs/nls'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface ITasksResourceContent { + tasks: string | null; +} + +export class TasksResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const tasksContent = await this.getTasksResourceContent(profile); + return JSON.stringify(tasksContent); + } + + async getTasksResourceContent(profile: IUserDataProfile): Promise { + const tasksContent = await this.getTasksContent(profile); + return { tasks: tasksContent }; + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const tasksContent: ITasksResourceContent = JSON.parse(content); + if (!tasksContent.tasks) { + this.logService.info(`Profile: No tasks to apply...`); + return; + } + await this.fileService.writeFile(profile.tasksResource, VSBuffer.fromString(tasksContent.tasks)); + } + + private async getTasksContent(profile: IUserDataProfile): Promise { + try { + const content = await this.fileService.readFile(profile.tasksResource); + return content.value.toString(); + } catch (error) { + // File not found + if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return null; + } else { + throw error; + } + } + } + +} + +export class TasksResourceTreeItem implements IProfileResourceTreeItem { + + readonly handle = this.profile.tasksResource.toString(); + readonly label = { label: localize('tasks', "User Tasks") }; + readonly collapsibleState = TreeItemCollapsibleState.None; + checkbox: ITreeItemCheckboxState | undefined = { isChecked: true }; + readonly command = { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.profile.tasksResource, undefined, undefined] + }; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { return undefined; } + + async hasContent(): Promise { + const tasksContent = await this.instantiationService.createInstance(TasksResource).getTasksResourceContent(this.profile); + return tasksContent.tasks !== null; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(TasksResource).getContent(this.profile); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts new file mode 100644 index 00000000000..2f2f0731ff3 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -0,0 +1,699 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import * as DOM from 'vs/base/browser/dom'; +import { IUserDataProfileImportExportService, PROFILE_FILTER, PROFILE_EXTENSION, IUserDataProfileContentHandler, IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT, PROFILES_TTILE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, IProfileResourceChildTreeItem, PROFILES_CATEGORY, isUserDataProfileTemplate, IUserDataProfileManagementService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ILogService } from 'vs/platform/log/common/log'; +import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { SettingsResource, SettingsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/settingsResource'; +import { KeybindingsResource, KeybindingsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/keybindingsResource'; +import { SnippetsResource, SnippetsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/snippetsResource'; +import { TasksResource, TasksResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/tasksResource'; +import { ExtensionsResource, ExtensionsResourceExportTreeItem, ExtensionsResourceImportTreeItem } from 'vs/workbench/services/userDataProfile/browser/extensionsResource'; +import { GlobalStateResource, GlobalStateResourceExportTreeItem, GlobalStateResourceImportTreeItem } from 'vs/workbench/services/userDataProfile/browser/globalStateResource'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorsOrder } from 'vs/workbench/common/editor'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { joinPath } from 'vs/base/common/resources'; + +interface IUserDataProfileTemplate { + readonly name: string; + readonly shortName?: string; + readonly settings?: string; + readonly keybindings?: string; + readonly tasks?: string; + readonly snippets?: string; + readonly globalState?: string; + readonly extensions?: string; +} + +export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService { + + readonly _serviceBrand: undefined; + + private profileContentHandlers = new Map(); + private readonly isProfileImportExportInProgressContextKey: IContextKey; + + private readonly viewContainer: ViewContainer; + private readonly fileUserDataProfileContentHandler: IUserDataProfileContentHandler; + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IViewsService private readonly viewsService: IViewsService, + @IEditorService private readonly editorService: IEditorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IFileService private readonly fileService: IFileService, + @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IExtensionService private readonly extensionService: IExtensionService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @INotificationService private readonly notificationService: INotificationService, + @IProgressService private readonly progressService: IProgressService, + @IDialogService private readonly dialogService: IDialogService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.registerProfileContentHandler(this.fileUserDataProfileContentHandler = instantiationService.createInstance(FileUserDataProfileContentHandler)); + this.isProfileImportExportInProgressContextKey = IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.bindTo(contextKeyService); + + this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: 'userDataProfiles', + title: PROFILES_TTILE, + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + ['userDataProfiles', { mergeViewWithContainerWhenSingleView: true }] + ), + icon: defaultUserDataProfileIcon, + hideIfEmpty: true, + }, ViewContainerLocation.Sidebar); + } + + registerProfileContentHandler(profileContentHandler: IUserDataProfileContentHandler): void { + if (this.profileContentHandlers.has(profileContentHandler.id)) { + throw new Error(`Profile content handler with id '${profileContentHandler.id}' already registered.`); + } + this.profileContentHandlers.set(profileContentHandler.id, profileContentHandler); + } + + async exportProfile(): Promise { + if (this.isProfileImportExportInProgressContextKey.get()) { + this.logService.warn('Profile import/export already in progress.'); + return; + } + + this.isProfileImportExportInProgressContextKey.set(true); + const disposables = new DisposableStore(); + + try { + disposables.add(toDisposable(() => this.isProfileImportExportInProgressContextKey.set(false))); + const userDataProfilesData = disposables.add(this.instantiationService.createInstance(UserDataProfileExportData, this.userDataProfileService.currentProfile)); + const exportProfile = await this.showProfilePreviewView(`workbench.views.profiles.export.preview`, localize('export profile preview', "Export"), userDataProfilesData); + if (exportProfile) { + const profileContent = await userDataProfilesData.getContent(); + const resource = await this.saveProfileContent(profileContent); + if (resource) { + this.notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY.value)); + } + } + } finally { + disposables.dispose(); + } + } + + async importProfile(uri: URI): Promise { + if (this.isProfileImportExportInProgressContextKey.get()) { + this.logService.warn('Profile import/export already in progress.'); + return; + } + + this.isProfileImportExportInProgressContextKey.set(true); + const disposables = new DisposableStore(); + disposables.add(toDisposable(() => this.isProfileImportExportInProgressContextKey.set(false))); + + try { + const profileContent = await this.resolveProfileContent(uri); + if (profileContent === null) { + return; + } + const profileTemplate: IUserDataProfileTemplate = JSON.parse(profileContent); + if (!isUserDataProfileTemplate(profileTemplate)) { + this.notificationService.error('Invalid profile content.'); + return; + } + const userDataProfilesData = disposables.add(this.instantiationService.createInstance(UserDataProfileImportData, profileTemplate)); + const importProfile = await this.showProfilePreviewView(`workbench.views.profiles.import.preview`, localize('import profile preview', "Import"), userDataProfilesData); + if (!importProfile) { + return; + } + const profile = await this.getProfileToImport(profileTemplate); + if (!profile) { + return; + } + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY.value), + }, async progress => { + if (profileTemplate.settings) { + await this.instantiationService.createInstance(SettingsResource).apply(profileTemplate.settings, profile); + } + if (profileTemplate.keybindings) { + await this.instantiationService.createInstance(KeybindingsResource).apply(profileTemplate.keybindings, profile); + } + if (profileTemplate.tasks) { + await this.instantiationService.createInstance(TasksResource).apply(profileTemplate.tasks, profile); + } + if (profileTemplate.snippets) { + await this.instantiationService.createInstance(SnippetsResource).apply(profileTemplate.snippets, profile); + } + if (profileTemplate.globalState) { + await this.instantiationService.createInstance(GlobalStateResource).apply(profileTemplate.globalState, profile); + } + if (profileTemplate.extensions) { + await this.instantiationService.createInstance(ExtensionsResource).apply(profileTemplate.extensions, profile); + } + await this.userDataProfileManagementService.switchProfile(profile); + }); + + this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY.value)); + } finally { + disposables.dispose(); + } + } + + private async saveProfileContent(content: string): Promise { + const profileContentHandler = await this.pickProfileContentHandler(); + if (!profileContentHandler) { + return null; + } + const resource = await profileContentHandler.saveProfile(content); + return resource; + } + + private async resolveProfileContent(resource: URI): Promise { + if (await this.fileService.canHandleResource(resource)) { + return this.fileUserDataProfileContentHandler.readProfile(resource); + } + await this.extensionService.activateByEvent(`onProfile:import:${resource.authority}`); + const profileContentHandler = this.profileContentHandlers.get(resource.authority); + return profileContentHandler?.readProfile(resource) ?? null; + } + + private async pickProfileContentHandler(): Promise { + if (this.profileContentHandlers.size === 1) { + return this.profileContentHandlers.values().next().value; + } + await this.extensionService.activateByEvent('onProfile:export'); + return undefined; + } + + private async getProfileToImport(profileTemplate: IUserDataProfileTemplate): Promise { + const profile = this.userDataProfilesService.profiles.find(p => p.name === profileTemplate.name); + if (profile) { + const confirmation = await this.dialogService.confirm({ + type: 'info', + message: localize('profile already exists', "Profile with name '{0}' already exists. Do you want to overwrite it?", profileTemplate.name), + primaryButton: localize('overwrite', "Overwrite"), + secondaryButton: localize('create new', "Create New Profile"), + }); + if (confirmation.confirmed) { + return profile; + } + const name = await this.quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title: localize('create new', "Create New Profile"), + validateInput: async (value: string) => { + if (this.userDataProfilesService.profiles.some(p => p.name === value)) { + return localize('profileExists', "Profile with name {0} already exists.", value); + } + return undefined; + } + }); + if (!name) { + return undefined; + } + return this.userDataProfilesService.createNamedProfile(name); + } else { + return this.userDataProfilesService.createNamedProfile(profileTemplate.name, { shortName: profileTemplate.shortName }); + } + } + + private async showProfilePreviewView(id: string, name: string, userDataProfilesData: UserDataProfileTreeViewData): Promise { + const disposables = new DisposableStore(); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + const treeView = disposables.add(this.instantiationService.createInstance(TreeView, id, name)); + treeView.showRefreshAction = true; + let onConfirm: (() => void) | undefined, onCancel: (() => void) | undefined; + const exportPreviewConfirmPomise = new Promise((c, e) => { onConfirm = c; onCancel = e; }); + const descriptor: ITreeViewDescriptor = { + id, + name, + ctorDescriptor: new SyncDescriptor(UserDataProfileExportViewPane, [userDataProfilesData, name, onConfirm, onCancel]), + canToggleVisibility: false, + canMoveView: false, + treeView, + collapsed: false, + }; + + try { + viewsRegistry.registerViews([descriptor], this.viewContainer); + await this.viewsService.openView(id, true); + await exportPreviewConfirmPomise; + return true; + } catch { + return false; + } finally { + viewsRegistry.deregisterViews([descriptor], this.viewContainer); + disposables.dispose(); + this.closeAllImportExportPreviewEditors().then(null, onUnexpectedError); + } + } + + private async closeAllImportExportPreviewEditors(): Promise { + const editorsToColse = this.editorService.getEditors(EditorsOrder.SEQUENTIAL).filter(({ editor }) => editor.resource?.scheme === USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME); + if (editorsToColse.length) { + await this.editorService.closeEditors(editorsToColse); + } + } + + async setProfile(profile: IUserDataProfileTemplate): Promise { + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('profiles.applying', "{0}: Applying...", PROFILES_CATEGORY.value), + }, async progress => { + if (profile.settings) { + await this.instantiationService.createInstance(SettingsResource).apply(profile.settings, this.userDataProfileService.currentProfile); + } + if (profile.globalState) { + await this.instantiationService.createInstance(GlobalStateResource).apply(profile.globalState, this.userDataProfileService.currentProfile); + } + if (profile.extensions) { + await this.instantiationService.createInstance(ExtensionsResource).apply(profile.extensions, this.userDataProfileService.currentProfile); + } + }); + this.notificationService.info(localize('applied profile', "{0}: Applied successfully.", PROFILES_CATEGORY.value)); + } + +} + +class FileUserDataProfileContentHandler implements IUserDataProfileContentHandler { + + readonly id = 'file'; + readonly name = localize('file', "File"); + + constructor( + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IFileService private readonly fileService: IFileService, + @ITextFileService private readonly textFileService: ITextFileService, + ) { } + + async saveProfile(content: string): Promise { + const profileLocation = await this.fileDialogService.showSaveDialog({ + title: localize('export profile dialog', "Save Profile"), + filters: PROFILE_FILTER, + defaultUri: this.uriIdentityService.extUri.joinPath(await this.fileDialogService.defaultFilePath(), `profile.${PROFILE_EXTENSION}`), + }); + if (!profileLocation) { + return null; + } + await this.textFileService.create([{ resource: profileLocation, value: content, options: { overwrite: true } }]); + return profileLocation; + } + + async readProfile(uri: URI): Promise { + return (await this.fileService.readFile(uri)).value.toString(); + } + + async selectProfile(): Promise { + const profileLocation = await this.fileDialogService.showOpenDialog({ + canSelectFolders: false, + canSelectFiles: true, + canSelectMany: false, + filters: PROFILE_FILTER, + title: localize('select profile', "Select Profile"), + }); + return profileLocation ? profileLocation[0] : null; + } + + +} + +class UserDataProfileExportViewPane extends TreeViewPane { + + private buttonsContainer!: HTMLElement; + private confirmButton!: Button; + private cancelButton!: Button; + private dimension: DOM.Dimension | undefined; + private totalTreeItemsCount: number = 0; + + constructor( + private readonly userDataProfileData: UserDataProfileTreeViewData, + private readonly confirmLabel: string, + private readonly onConfirm: () => void, + private readonly onCancel: () => void, + options: IViewletViewOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, notificationService); + } + + + protected override renderTreeView(container: HTMLElement): void { + this.treeView.dataProvider = this.userDataProfileData; + super.renderTreeView(DOM.append(container, DOM.$(''))); + this.createButtons(container); + this._register(this.treeView.onDidChangeCheckboxState(items => { + this.treeView.refresh(this.userDataProfileData.onDidChangeCheckboxState(items)); + this.updateConfirmButtonEnablement(); + })); + this.userDataProfileData.getExpandedItemsCount().then(count => { + this.totalTreeItemsCount = count; + if (this.dimension) { + this.layoutTreeView(this.dimension.height, this.dimension.width); + } + }); + } + + private createButtons(container: HTMLElement): void { + this.buttonsContainer = DOM.append(container, DOM.$('.manual-sync-buttons-container')); + + this.confirmButton = this._register(new Button(this.buttonsContainer, { ...defaultButtonStyles })); + this.confirmButton.label = this.confirmLabel; + this._register(this.confirmButton.onDidClick(() => this.onConfirm())); + + this.cancelButton = this._register(new Button(this.buttonsContainer, { secondary: true, ...defaultButtonStyles })); + this.cancelButton.label = localize('cancel', "Cancel"); + this._register(this.cancelButton.onDidClick(() => this.onCancel())); + } + + + protected override layoutTreeView(height: number, width: number): void { + this.dimension = new DOM.Dimension(width, height); + const buttonContainerHeight = 78; + this.buttonsContainer.style.height = `${buttonContainerHeight}px`; + this.buttonsContainer.style.width = `${width}px`; + + super.layoutTreeView(Math.min(height - buttonContainerHeight, 22 * (this.totalTreeItemsCount || 12)), width); + } + + private updateConfirmButtonEnablement(): void { + this.confirmButton.enabled = this.userDataProfileData.isEnabled(); + } + +} + +const USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME = 'userdataprofileexportpreview'; + +abstract class UserDataProfileTreeViewData extends Disposable implements ITreeViewDataProvider { + + async getExpandedItemsCount(): Promise { + const roots = await this.getRoots(); + const children = await Promise.all(roots.map(async root => { + if (root.collapsibleState === TreeItemCollapsibleState.Expanded) { + const children = await root.getChildren(); + return children ?? []; + } + return []; + })); + return roots.length + children.flat().length; + } + + private rootsPromise: Promise | undefined; + async getChildren(element?: ITreeItem): Promise { + if (element) { + return (element).getChildren(); + } else { + this.rootsPromise = undefined; + return this.getRoots(); + } + } + + private getRoots(): Promise { + if (!this.rootsPromise) { + this.rootsPromise = this.fetchRoots(); + } + return this.rootsPromise; + } + + abstract isEnabled(): boolean; + abstract onDidChangeCheckboxState(items: ITreeItem[]): ITreeItem[]; + protected abstract fetchRoots(): Promise; +} + +class UserDataProfileExportData extends UserDataProfileTreeViewData implements ITreeViewDataProvider { + + private settingsResourceTreeItem: SettingsResourceTreeItem | undefined; + private keybindingsResourceTreeItem: KeybindingsResourceTreeItem | undefined; + private tasksResourceTreeItem: TasksResourceTreeItem | undefined; + private snippetsResourceTreeItem: SnippetsResourceTreeItem | undefined; + private extensionsResourceTreeItem: ExtensionsResourceExportTreeItem | undefined; + private globalStateResourceTreeItem: GlobalStateResourceExportTreeItem | undefined; + + private readonly disposables = this._register(new DisposableStore()); + + constructor( + private readonly profile: IUserDataProfile, + @IFileService private readonly fileService: IFileService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + } + + onDidChangeCheckboxState(items: ITreeItem[]): ITreeItem[] { + const toRefresh: ITreeItem[] = []; + for (const item of items) { + if (item.children) { + for (const child of item.children) { + if (child.checkbox) { + child.checkbox.isChecked = !!item.checkbox?.isChecked; + } + } + toRefresh.push(item); + } else { + const parent = (item).parent; + if (item.checkbox?.isChecked && parent?.checkbox) { + parent.checkbox.isChecked = true; + toRefresh.push(parent); + } + } + } + return items; + } + + protected async fetchRoots(): Promise { + this.disposables.clear(); + this.disposables.add(this.fileService.registerProvider(USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME, this._register(new InMemoryFileSystemProvider()))); + const roots: IProfileResourceTreeItem[] = []; + const exportPreviewProfle = this.createExportPreviewProfile(this.profile); + + const settingsResource = this.instantiationService.createInstance(SettingsResource); + const settingsContent = await settingsResource.getContent(this.profile); + await settingsResource.apply(settingsContent, exportPreviewProfle); + this.settingsResourceTreeItem = this.instantiationService.createInstance(SettingsResourceTreeItem, exportPreviewProfle); + if (await this.settingsResourceTreeItem.hasContent()) { + roots.push(this.settingsResourceTreeItem); + } + + const keybindingsResource = this.instantiationService.createInstance(KeybindingsResource); + const keybindingsContent = await keybindingsResource.getContent(this.profile); + await keybindingsResource.apply(keybindingsContent, exportPreviewProfle); + this.keybindingsResourceTreeItem = this.instantiationService.createInstance(KeybindingsResourceTreeItem, exportPreviewProfle); + if (await this.keybindingsResourceTreeItem.hasContent()) { + roots.push(this.keybindingsResourceTreeItem); + } + + const tasksResource = this.instantiationService.createInstance(TasksResource); + const tasksContent = await tasksResource.getContent(this.profile); + await tasksResource.apply(tasksContent, exportPreviewProfle); + this.tasksResourceTreeItem = this.instantiationService.createInstance(TasksResourceTreeItem, exportPreviewProfle); + if (await this.tasksResourceTreeItem.hasContent()) { + roots.push(this.tasksResourceTreeItem); + } + + const snippetsResource = this.instantiationService.createInstance(SnippetsResource); + const snippetsContent = await snippetsResource.getContent(this.profile); + await snippetsResource.apply(snippetsContent, exportPreviewProfle); + this.snippetsResourceTreeItem = this.instantiationService.createInstance(SnippetsResourceTreeItem, exportPreviewProfle); + if (await this.snippetsResourceTreeItem.hasContent()) { + roots.push(this.snippetsResourceTreeItem); + } + + this.globalStateResourceTreeItem = this.instantiationService.createInstance(GlobalStateResourceExportTreeItem, exportPreviewProfle); + if (await this.globalStateResourceTreeItem.hasContent()) { + roots.push(this.globalStateResourceTreeItem); + } + + this.extensionsResourceTreeItem = this.instantiationService.createInstance(ExtensionsResourceExportTreeItem, exportPreviewProfle); + if (await this.extensionsResourceTreeItem.hasContent()) { + roots.push(this.extensionsResourceTreeItem); + } + + return roots; + } + + private createExportPreviewProfile(profile: IUserDataProfile): IUserDataProfile { + return { + id: profile.id, + name: profile.name, + location: profile.location, + isDefault: profile.isDefault, + shortName: profile.shortName, + globalStorageHome: profile.globalStorageHome, + settingsResource: profile.settingsResource.with({ scheme: USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME }), + keybindingsResource: profile.keybindingsResource.with({ scheme: USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME }), + tasksResource: profile.tasksResource.with({ scheme: USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME }), + snippetsHome: profile.snippetsHome.with({ scheme: USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME }), + extensionsResource: profile.extensionsResource, + useDefaultFlags: profile.useDefaultFlags, + isTransient: profile.isTransient + }; + } + + async getContent(): Promise { + const settings = this.settingsResourceTreeItem?.checkbox?.isChecked ? await this.settingsResourceTreeItem.getContent() : undefined; + const keybindings = this.keybindingsResourceTreeItem?.checkbox?.isChecked ? await this.keybindingsResourceTreeItem.getContent() : undefined; + const tasks = this.tasksResourceTreeItem?.checkbox?.isChecked ? await this.tasksResourceTreeItem.getContent() : undefined; + const snippets = this.snippetsResourceTreeItem?.checkbox?.isChecked ? await this.snippetsResourceTreeItem.getContent() : undefined; + const extensions = this.extensionsResourceTreeItem?.checkbox?.isChecked ? await this.extensionsResourceTreeItem.getContent() : undefined; + const globalState = this.globalStateResourceTreeItem?.checkbox?.isChecked ? await this.globalStateResourceTreeItem.getContent() : undefined; + const profile: IUserDataProfileTemplate = { + name: this.profile.name, + shortName: this.profile.shortName, + settings, + keybindings, + tasks, + snippets, + extensions, + globalState + }; + return JSON.stringify(profile); + } + + isEnabled(): boolean { + return !!this.settingsResourceTreeItem?.checkbox?.isChecked + || !!this.keybindingsResourceTreeItem?.checkbox?.isChecked + || !!this.tasksResourceTreeItem?.checkbox?.isChecked + || !!this.snippetsResourceTreeItem?.checkbox?.isChecked + || !!this.extensionsResourceTreeItem?.checkbox?.isChecked + || !!this.globalStateResourceTreeItem?.checkbox?.isChecked; + } + +} + +class UserDataProfileImportData extends UserDataProfileTreeViewData implements ITreeViewDataProvider { + + private settingsResourceTreeItem: SettingsResourceTreeItem | undefined; + private keybindingsResourceTreeItem: KeybindingsResourceTreeItem | undefined; + private tasksResourceTreeItem: TasksResourceTreeItem | undefined; + private snippetsResourceTreeItem: SnippetsResourceTreeItem | undefined; + private extensionsResourceTreeItem: ExtensionsResourceImportTreeItem | undefined; + private globalStateResourceTreeItem: GlobalStateResourceImportTreeItem | undefined; + + private readonly disposables = this._register(new DisposableStore()); + + constructor( + private readonly profile: IUserDataProfileTemplate, + @IFileService private readonly fileService: IFileService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + } + + onDidChangeCheckboxState(items: ITreeItem[]): ITreeItem[] { + return items; + } + + protected async fetchRoots(): Promise { + this.disposables.clear(); + + const inMemoryProvider = this._register(new InMemoryFileSystemProvider()); + this.disposables.add(this.fileService.registerProvider(USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME, inMemoryProvider)); + const roots: IProfileResourceTreeItem[] = []; + const importPreviewProfle = toUserDataProfile(generateUuid(), this.profile.name, URI.file('/root').with({ scheme: USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME })); + + this.settingsResourceTreeItem = undefined; + if (this.profile.settings) { + const settingsResource = this.instantiationService.createInstance(SettingsResource); + await settingsResource.apply(this.profile.settings, importPreviewProfle); + this.settingsResourceTreeItem = this.instantiationService.createInstance(SettingsResourceTreeItem, importPreviewProfle); + this.settingsResourceTreeItem.checkbox = undefined; + roots.push(this.settingsResourceTreeItem); + } + + this.keybindingsResourceTreeItem = undefined; + if (this.profile.keybindings) { + const keybindingsResource = this.instantiationService.createInstance(KeybindingsResource); + await keybindingsResource.apply(this.profile.keybindings, importPreviewProfle); + this.keybindingsResourceTreeItem = this.instantiationService.createInstance(KeybindingsResourceTreeItem, importPreviewProfle); + this.keybindingsResourceTreeItem.checkbox = undefined; + roots.push(this.keybindingsResourceTreeItem); + } + + this.tasksResourceTreeItem = undefined; + if (this.profile.tasks) { + const tasksResource = this.instantiationService.createInstance(TasksResource); + await tasksResource.apply(this.profile.tasks, importPreviewProfle); + this.tasksResourceTreeItem = this.instantiationService.createInstance(TasksResourceTreeItem, importPreviewProfle); + this.tasksResourceTreeItem.checkbox = undefined; + roots.push(this.tasksResourceTreeItem); + } + + this.snippetsResourceTreeItem = undefined; + if (this.profile.snippets) { + const snippetsResource = this.instantiationService.createInstance(SnippetsResource); + await snippetsResource.apply(this.profile.snippets, importPreviewProfle); + this.snippetsResourceTreeItem = this.instantiationService.createInstance(SnippetsResourceTreeItem, importPreviewProfle); + this.snippetsResourceTreeItem.checkbox = undefined; + roots.push(this.snippetsResourceTreeItem); + } + + this.globalStateResourceTreeItem = undefined; + if (this.profile.globalState) { + const globalStateResource = joinPath(importPreviewProfle.globalStorageHome, 'globalState.json'); + await this.fileService.writeFile(globalStateResource, VSBuffer.fromString(JSON.stringify(JSON.parse(this.profile.globalState), null, '\t'))); + this.globalStateResourceTreeItem = this.instantiationService.createInstance(GlobalStateResourceImportTreeItem, globalStateResource); + roots.push(this.globalStateResourceTreeItem); + } + + this.extensionsResourceTreeItem = undefined; + if (this.profile.extensions) { + this.extensionsResourceTreeItem = this.instantiationService.createInstance(ExtensionsResourceImportTreeItem, this.profile.extensions); + roots.push(this.extensionsResourceTreeItem); + } + + inMemoryProvider.setReadOnly(true); + + return roots; + } + + isEnabled(): boolean { + return true; + } + +} + +registerSingleton(IUserDataProfileImportExportService, UserDataProfileImportExportService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataProfile/common/extensionsResource.ts b/src/vs/workbench/services/userDataProfile/common/extensionsResource.ts deleted file mode 100644 index 101a537d12e..00000000000 --- a/src/vs/workbench/services/userDataProfile/common/extensionsResource.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IProfileResource } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; - -interface IProfileExtension { - identifier: IExtensionIdentifier; - preRelease?: boolean; - disabled?: boolean; -} - -export class ExtensionsResource implements IProfileResource { - - constructor( - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @ILogService private readonly logService: ILogService, - ) { - } - - async getContent(): Promise { - const extensions = await this.getLocalExtensions(); - return JSON.stringify(extensions); - } - - async apply(content: string): Promise { - const profileExtensions: IProfileExtension[] = JSON.parse(content); - const installedExtensions = await this.extensionManagementService.getInstalled(); - const extensionsToEnableOrDisable: { extension: ILocalExtension; enablementState: EnablementState }[] = []; - const extensionsToInstall: IProfileExtension[] = []; - for (const e of profileExtensions) { - const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier)); - if (!installedExtension || installedExtension.preRelease !== e.preRelease) { - extensionsToInstall.push(e); - } - if (installedExtension && this.extensionEnablementService.isEnabled(installedExtension) !== !e.disabled) { - extensionsToEnableOrDisable.push({ extension: installedExtension, enablementState: e.disabled ? EnablementState.DisabledGlobally : EnablementState.EnabledGlobally }); - } - } - const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => extension.type === ExtensionType.User && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); - for (const { extension, enablementState } of extensionsToEnableOrDisable) { - this.logService.trace(`Profile: Updating extension enablement...`, extension.identifier.id); - await this.extensionEnablementService.setEnablement([extension], enablementState); - this.logService.info(`Profile: Updated extension enablement`, extension.identifier.id); - } - if (extensionsToInstall.length) { - const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, hasPreRelease: e.preRelease })), CancellationToken.None); - await Promise.all(extensionsToInstall.map(async e => { - const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier)); - if (!extension) { - return; - } - if (await this.extensionManagementService.canInstall(extension)) { - this.logService.trace(`Profile: Installing extension...`, e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* set isMachineScoped value to prevent install and sync dialog in web */); - this.logService.info(`Profile: Installed extension.`, e.identifier.id, extension.version); - } else { - this.logService.info(`Profile: Skipped installing extension because it cannot be installed.`, extension.displayName || extension.identifier.id); - } - })); - } - if (extensionsToUninstall.length) { - await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e))); - } - } - - private async getLocalExtensions(): Promise { - const result: IProfileExtension[] = []; - const installedExtensions = await this.extensionManagementService.getInstalled(undefined); - for (const extension of installedExtensions) { - const { identifier, preRelease } = extension; - const enablementState = this.extensionEnablementService.getEnablementState(extension); - const disabled = !this.extensionEnablementService.isEnabledEnablementState(enablementState); - if (!disabled && extension.type === ExtensionType.System) { - // skip enabled system extensions - continue; - } - if (disabled && enablementState !== EnablementState.DisabledGlobally && enablementState !== EnablementState.DisabledWorkspace) { - //skip extensions that are not disabled by user - continue; - } - const profileExtension: IProfileExtension = { identifier }; - if (disabled) { - profileExtension.disabled = true; - } - if (preRelease) { - profileExtension.preRelease = true; - } - result.push(profileExtension); - } - return result; - } -} diff --git a/src/vs/workbench/services/userDataProfile/common/globalStateResource.ts b/src/vs/workbench/services/userDataProfile/common/globalStateResource.ts deleted file mode 100644 index d9f439a9ad6..00000000000 --- a/src/vs/workbench/services/userDataProfile/common/globalStateResource.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IStringDictionary } from 'vs/base/common/collections'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IProfileResource } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; - -interface IGlobalState { - storage: IStringDictionary; -} - -export class GlobalStateResource implements IProfileResource { - - constructor( - @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService, - ) { - } - - async getContent(): Promise { - const globalState = await this.getLocalGlobalState(); - return JSON.stringify(globalState); - } - - async apply(content: string): Promise { - const globalState: IGlobalState = JSON.parse(content); - await this.writeLocalGlobalState(globalState); - } - - private async getLocalGlobalState(): Promise { - const storage: IStringDictionary = {}; - for (const { key } of Registry.as(Extensions.ProfileStorageRegistry).all) { - const value = this.storageService.get(key, StorageScope.PROFILE); - if (value) { - storage[key] = value; - } - } - return { storage }; - } - - private async writeLocalGlobalState(globalState: IGlobalState): Promise { - const profileKeys: string[] = Object.keys(globalState.storage); - const updatedStorage: IStringDictionary = globalState.storage; - for (const { key } of Registry.as(Extensions.ProfileStorageRegistry).all) { - if (!profileKeys.includes(key)) { - // Remove the key if it does not exist in the profile - updatedStorage[key] = undefined; - } - } - const updatedStorageKeys: string[] = Object.keys(updatedStorage); - if (updatedStorageKeys.length) { - this.logService.trace(`Profile: Updating global state...`); - for (const key of updatedStorageKeys) { - this.storageService.store(key, globalState.storage[key], StorageScope.PROFILE, StorageTarget.USER); - } - this.logService.info(`Profile: Updated global state`, updatedStorageKeys); - } - } -} diff --git a/src/vs/workbench/services/userDataProfile/common/settingsResource.ts b/src/vs/workbench/services/userDataProfile/common/settingsResource.ts deleted file mode 100644 index 88b1b25733e..00000000000 --- a/src/vs/workbench/services/userDataProfile/common/settingsResource.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from 'vs/base/common/buffer'; -import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IUserDataProfileService, IProfileResource, ProfileCreationOptions } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { removeComments, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; - -interface ISettingsContent { - settings: string; -} - -export class SettingsResource implements IProfileResource { - - constructor( - @IFileService private readonly fileService: IFileService, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, - @ILogService private readonly logService: ILogService, - ) { - } - - async getContent(options?: ProfileCreationOptions): Promise { - const ignoredSettings = this.getIgnoredSettings(); - const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.userDataProfileService.currentProfile.settingsResource); - const localContent = await this.getLocalFileContent(); - let settings = updateIgnoredSettings(localContent || '{}', '{}', ignoredSettings, formattingOptions); - if (options?.skipComments) { - settings = removeComments(settings, formattingOptions); - } - const settingsContent: ISettingsContent = { settings }; - return JSON.stringify(settingsContent); - } - - async apply(content: string): Promise { - const settingsContent: ISettingsContent = JSON.parse(content); - this.logService.trace(`Profile: Applying settings...`); - const localSettingsContent = await this.getLocalFileContent(); - const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.userDataProfileService.currentProfile.settingsResource); - const contentToUpdate = updateIgnoredSettings(settingsContent.settings, localSettingsContent || '{}', this.getIgnoredSettings(), formattingOptions); - await this.fileService.writeFile(this.userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString(contentToUpdate)); - this.logService.info(`Profile: Applied settings`); - } - - private getIgnoredSettings(): string[] { - const allSettings = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const ignoredSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.MACHINE || allSettings[key]?.scope === ConfigurationScope.MACHINE_OVERRIDABLE); - return ignoredSettings; - } - - private async getLocalFileContent(): Promise { - try { - const content = await this.fileService.readFile(this.userDataProfileService.currentProfile.settingsResource); - return content.value.toString(); - } catch (error) { - return null; - } - } - -} diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 0109ce3c737..63b9032ad85 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -10,6 +10,10 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfileUpdateOptions } from 'vs/platform/userDataProfile/common/userDataProfile'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { URI } from 'vs/base/common/uri'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Codicon } from 'vs/base/common/codicons'; +import { ITreeItem } from 'vs/workbench/common/views'; export interface DidChangeUserDataProfileEvent { readonly preserveData: boolean; @@ -42,6 +46,9 @@ export interface IUserDataProfileManagementService { export interface IUserDataProfileTemplate { readonly settings?: string; + readonly keybindings?: string; + readonly tasks?: string; + readonly snippets?: string; readonly globalState?: string; readonly extensions?: string; } @@ -61,16 +68,36 @@ export const IUserDataProfileImportExportService = createDecorator; - importProfile(profile: IUserDataProfileTemplate): Promise; + registerProfileContentHandler(profileContentHandler: IUserDataProfileContentHandler): void; + + exportProfile(): Promise; + importProfile(uri: URI): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } export interface IProfileResource { - getContent(): Promise; - apply(content: string): Promise; + getContent(profile: IUserDataProfile): Promise; + apply(content: string, profile: IUserDataProfile): Promise; } +export interface IProfileResourceTreeItem extends ITreeItem { + getChildren(): Promise; +} + +export interface IProfileResourceChildTreeItem extends ITreeItem { + parent: IProfileResourceTreeItem; +} + +export interface IUserDataProfileContentHandler { + readonly id: string; + readonly name: string; + readonly description?: string; + saveProfile(content: string): Promise; + readProfile(uri: URI): Promise; +} + +export const defaultUserDataProfileIcon = registerIcon('defaultProfile-icon', Codicon.settings, localize('defaultProfileIcon', 'Icon for Default Profile.')); + export const ManageProfilesSubMenu = new MenuId('Profiles'); export const MANAGE_PROFILES_ACTION_ID = 'workbench.profiles.actions.manage'; export const PROFILES_TTILE = { value: localize('profiles', "Profiles"), original: 'Profiles' }; @@ -81,3 +108,4 @@ export const PROFILES_ENABLEMENT_CONTEXT = new RawContextKey('profiles. export const CURRENT_PROFILE_CONTEXT = new RawContextKey('currentProfile', ''); export const IS_CURRENT_PROFILE_TRANSIENT_CONTEXT = new RawContextKey('isCurrentProfileTransient', false); export const HAS_PROFILES_CONTEXT = new RawContextKey('hasProfiles', false); +export const IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT = new RawContextKey('isProfileImportExportInProgress', false); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts deleted file mode 100644 index bbeb2e00a12..00000000000 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts +++ /dev/null @@ -1,96 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { ExtensionsResource } from 'vs/workbench/services/userDataProfile/common/extensionsResource'; -import { GlobalStateResource } from 'vs/workbench/services/userDataProfile/common/globalStateResource'; -import { IUserDataProfileTemplate, IUserDataProfileImportExportService, PROFILES_CATEGORY, IUserDataProfileManagementService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { SettingsResource } from 'vs/workbench/services/userDataProfile/common/settingsResource'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; - -export class UserDataProfileImportExportService implements IUserDataProfileImportExportService { - - readonly _serviceBrand: undefined; - - private readonly settingsResourceProfile: SettingsResource; - private readonly globalStateProfile: GlobalStateResource; - private readonly extensionsProfile: ExtensionsResource; - - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IProgressService private readonly progressService: IProgressService, - @INotificationService private readonly notificationService: INotificationService, - @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - ) { - this.settingsResourceProfile = instantiationService.createInstance(SettingsResource); - this.globalStateProfile = instantiationService.createInstance(GlobalStateResource); - this.extensionsProfile = instantiationService.createInstance(ExtensionsResource); - } - - async exportProfile(options?: { skipComments: boolean }): Promise { - const settings = await this.settingsResourceProfile.getContent(options); - const globalState = await this.globalStateProfile.getContent(); - const extensions = await this.extensionsProfile.getContent(); - return { - settings, - globalState, - extensions - }; - } - - async importProfile(profileTemplate: IUserDataProfileTemplate): Promise { - const name = await this.quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title: localize('save profile as', "Create from Current Profile..."), - }); - if (!name) { - return undefined; - } - - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY.value), - }, async progress => { - await this.userDataProfileManagementService.createAndEnterProfile(name); - if (profileTemplate.settings) { - await this.settingsResourceProfile.apply(profileTemplate.settings); - } - if (profileTemplate.globalState) { - await this.globalStateProfile.apply(profileTemplate.globalState); - } - if (profileTemplate.extensions) { - await this.extensionsProfile.apply(profileTemplate.extensions); - } - }); - - this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY.value)); - } - - async setProfile(profile: IUserDataProfileTemplate): Promise { - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('profiles.applying', "{0}: Applying...", PROFILES_CATEGORY.value), - }, async progress => { - if (profile.settings) { - await this.settingsResourceProfile.apply(profile.settings); - } - if (profile.globalState) { - await this.globalStateProfile.apply(profile.globalState); - } - if (profile.extensions) { - await this.extensionsProfile.apply(profile.extensions); - } - }); - this.notificationService.info(localize('applied profile', "{0}: Applied successfully.", PROFILES_CATEGORY.value)); - } - -} - -registerSingleton(IUserDataProfileImportExportService, UserDataProfileImportExportService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index 37727ac08a3..9966a1cda99 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -4,15 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Promises } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; -import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; - -const defaultUserDataProfileIcon = registerIcon('defaultProfile-icon', Codicon.settings, localize('defaultProfileIcon', 'Icon for Default Profile.')); +import { defaultUserDataProfileIcon, DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class UserDataProfileService extends Disposable implements IUserDataProfileService { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts deleted file mode 100644 index 807b20e259b..00000000000 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; - -export namespace Extensions { - export const ProfileStorageRegistry = 'workbench.registry.profile.storage'; -} - -export interface IProfileStorageKey { - readonly key: string; - readonly description?: string; -} - -/** - * A registry for storage keys used for profiles - */ -export interface IProfileStorageRegistry { - /** - * An event that is triggered when storage keys are registered. - */ - readonly onDidRegister: Event; - - /** - * All registered storage keys - */ - readonly all: IProfileStorageKey[]; - - /** - * Register profile storage keys - * - * @param keys keys to register - */ - registerKeys(keys: IProfileStorageKey[]): void; -} - -class ProfileStorageRegistryImpl extends Disposable implements IProfileStorageRegistry { - - private readonly _onDidRegister = this._register(new Emitter()); - readonly onDidRegister = this._onDidRegister.event; - - private readonly storageKeys = new Map(); - - get all(): IProfileStorageKey[] { - return [...this.storageKeys.values()].flat(); - } - - registerKeys(keys: IProfileStorageKey[]): void { - for (const key of keys) { - this.storageKeys.set(key.key, key); - } - this._onDidRegister.fire(keys); - } - -} - -Registry.add(Extensions.ProfileStorageRegistry, new ProfileStorageRegistryImpl()); - diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 12750975fc0..19b35dc95f2 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -19,7 +19,6 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { getViewsStateStorageId, ViewContainerModel } from 'vs/workbench/services/views/common/viewContainerModel'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; import { IStringDictionary } from 'vs/base/common/collections'; interface IViewsCustomizations { @@ -114,11 +113,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions())); - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: ViewDescriptorService.VIEWS_CUSTOMIZATIONS, - description: localize('views customizations', "Views Customizations"), - }]); } private migrateToViewsCustomizationsStorage(): void { diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index 82c767f520f..6b8593224a1 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -16,7 +16,6 @@ import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; import { isEqual, joinPath } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; import { localize } from 'vs/nls'; import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -128,11 +127,6 @@ class ViewDescriptorsState extends Disposable { this.state = this.initialize(); - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: this.globalViewsStateStorageId, - description: localize('globalViewsStateStorageId', "Views visibility customizations in {0} view container", viewContainerName), - }]); } set(id: string, state: IViewDescriptorState): void { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 45eeada71ea..b36112b6b94 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1351,10 +1351,11 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { } export class TestInMemoryFileSystemProvider extends InMemoryFileSystemProvider implements IFileSystemProviderWithFileReadStreamCapability { - override readonly capabilities: FileSystemProviderCapabilities = - FileSystemProviderCapabilities.FileReadWrite - | FileSystemProviderCapabilities.PathCaseSensitive - | FileSystemProviderCapabilities.FileReadStream; + override get capabilities(): FileSystemProviderCapabilities { + return FileSystemProviderCapabilities.FileReadWrite + | FileSystemProviderCapabilities.PathCaseSensitive + | FileSystemProviderCapabilities.FileReadStream; + } readFileStream(resource: URI): ReadableStreamEvents { const BUFFER_SIZE = 64 * 1024; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 1d03361f7cb..0298a6c9ee6 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -84,7 +84,7 @@ import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRe import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; -import 'vs/workbench/services/userDataProfile/common/userDataProfileImportExportService'; +import 'vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileManagement'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 3876bc2ec98..d097639f885 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -85,6 +85,7 @@ import 'vs/workbench/services/search/electron-sandbox/searchService'; import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService'; +import 'vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; From 0182a4bf399cdacce7c112cbe499167eac759b41 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 16 Nov 2022 16:41:54 -0800 Subject: [PATCH 24/67] Kernel picker type: all/mru. (#166523) --- .../notebook/browser/notebook.contribution.ts | 13 +++++++++---- .../browser/viewParts/notebookKernelView.ts | 8 ++++---- .../contrib/notebook/common/notebookCommon.ts | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 88009aa22df..c52a7128365 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -910,11 +910,16 @@ configurationRegistry.registerConfiguration({ type: 'string', tags: ['notebookLayout'] }, - [NotebookSetting.kernelPickerMRU]: { - markdownDescription: nls.localize('notebook.kernelPickerMRU', "Controls whether the kernel picker should show the most recently used kernels."), - type: 'boolean', + [NotebookSetting.kernelPickerType]: { + markdownDescription: nls.localize('notebook.kernelPickerType', "Controls the type of kernel picker to use."), + type: 'string', + enum: ['all', 'mru'], + enumDescriptions: [ + nls.localize('notebook.kernelPickerType.all', "Show all kernels."), + nls.localize('notebook.kernelPickerType.mru', "Experiment: show recently used kernels."), + ], tags: ['notebookLayout'], - default: false + default: 'all' } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 6392cf58542..fee5eecb3d8 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -80,9 +80,9 @@ registerAction2(class extends Action2 { const instantiationService = accessor.get(IInstantiationService); const configurationService = accessor.get(IConfigurationService); - const usingMru = configurationService.getValue('notebook.experimental.kernelPicker.mru'); + const kernelPickerType = configurationService.getValue<'all' | 'mru'>('notebook.kernelPicker.type'); - if (usingMru) { + if (kernelPickerType === 'mru') { const strategy = instantiationService.createInstance(KernelPickerMRUStrategy); return await strategy.showQuickPick(context); } else { @@ -139,8 +139,8 @@ export class NotebooKernelActionViewItem extends ActionViewItem { return; } - const usingMru = this._configurationService.getValue('notebook.experimental.kernelPicker.mru'); - if (usingMru) { + const kernelPickerType = this._configurationService.getValue<'all' | 'mru'>('notebook.kernelPicker.type'); + if (kernelPickerType === 'mru') { KernelPickerMRUStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService); } else { KernelPickerFlatStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService, this._editor.scopedContextKeyService); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a4d954b227b..9cf0175219e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -926,7 +926,7 @@ export const NotebookSetting = { outputLineHeight: 'notebook.outputLineHeight', outputFontSize: 'notebook.outputFontSize', outputFontFamily: 'notebook.outputFontFamily', - kernelPickerMRU: 'notebook.experimental.kernelPicker.mru' + kernelPickerType: 'notebook.kernelPicker.type' } as const; export const enum CellStatusbarAlignment { From 8cae5f6bfe67755d0a6ce254d81da468a4e93d0d Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 16 Nov 2022 19:16:04 -0800 Subject: [PATCH 25/67] use correct icons, remove ellipsis (#166493) --- .../browser/terminalQuickFixBuiltinActions.ts | 7 +++-- .../widgets/terminalQuickFixMenuItems.ts | 20 +++++++++---- .../terminal/browser/xterm/quickFixAddon.ts | 29 +++++++------------ .../test/browser/quickFixAddon.test.ts | 2 +- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts index 672cc8ec763..0b36e7145ac 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts @@ -7,6 +7,7 @@ import { localize } from 'vs/nls'; import { TerminalQuickFixMatchResult, ITerminalQuickFixOptions, ITerminalInstance, TerminalQuickFixAction } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalCommand } from 'vs/workbench/contrib/terminal/common/terminal'; import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal'; +import { TerminalQuickFixType } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems'; export const GitCommandLineRegex = /git/; export const GitPushCommandLineRegex = /git\s+push/; export const GitTwoDashesRegex = /error: did you mean `--(.+)` \(with two dashes\)\?/; @@ -41,7 +42,7 @@ export function gitSimilar(): ITerminalQuickFixOptions { if (fixedCommand) { actions.push({ id: 'Git Similar', - type: 'command', + type: TerminalQuickFixType.Command, command: command.command.replace(/git\s+[^\s]+/, `git ${fixedCommand}`), addNewLine: true }); @@ -70,7 +71,7 @@ export function gitTwoDashes(): ITerminalQuickFixOptions { return; } return { - type: 'command', + type: TerminalQuickFixType.Command, id: 'Git Two Dashes', command: command.command.replace(` -${problemArg}`, ` --${problemArg}`), addNewLine: true @@ -97,7 +98,7 @@ export function freePort(terminalInstance?: Partial): ITermin } const label = localize("terminal.freePort", "Free port {0}", port); return { - class: undefined, + class: TerminalQuickFixType.Port, tooltip: label, id: 'terminal.freePort', label, diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts b/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts index 45761718426..32eca711af7 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts @@ -10,14 +10,22 @@ import { localize } from 'vs/nls'; import { ActionListItemKind, IListMenuItem } from 'vs/platform/actionWidget/browser/actionList'; import { IActionItem } from 'vs/platform/actionWidget/common/actionWidget'; +export const enum TerminalQuickFixType { + Command = 'command', + Opener = 'opener', + Port = 'port' +} + export class TerminalQuickFix implements IActionItem { action: IAction; + type: string; disabled?: boolean; title?: string; - constructor(action: IAction, title?: string, disabled?: boolean) { + constructor(action: IAction, type: string, title?: string, disabled?: boolean) { this.action = action; this.disabled = disabled; this.title = title; + this.type = type; } } @@ -28,7 +36,7 @@ export function toMenuItems(inputQuickFixes: readonly TerminalQuickFix[], showHe kind: ActionListItemKind.Header, group: { kind: CodeActionKind.QuickFix, - title: localize('codeAction.widget.id.quickfix', 'Quick Fix...') + title: localize('codeAction.widget.id.quickfix', 'Quick Fix') } }); for (const quickFix of showHeaders ? inputQuickFixes : inputQuickFixes.filter(i => !!i.action)) { @@ -50,13 +58,13 @@ export function toMenuItems(inputQuickFixes: readonly TerminalQuickFix[], showHe } function getQuickFixIcon(quickFix: TerminalQuickFix): { codicon: Codicon } { - switch (quickFix.action.id) { - case 'quickFix.opener': + switch (quickFix.type) { + case TerminalQuickFixType.Opener: // TODO: if it's a file link, use the open file icon return { codicon: Codicon.link }; - case 'quickFix.command': + case TerminalQuickFixType.Command: return { codicon: Codicon.run }; - case 'quickFix.freePort': + case TerminalQuickFixType.Port: return { codicon: Codicon.debugDisconnect }; } return { codicon: Codicon.lightBulb }; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts index 7e64705a746..2ff6d5724ef 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts @@ -24,11 +24,9 @@ import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal import { URI } from 'vs/base/common/uri'; import { gitCreatePr, gitPushSetUpstream, gitSimilar } from 'vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; -import { TerminalQuickFix, toMenuItems } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems'; -import { previewSelectedActionCommand } from 'vs/platform/actionWidget/browser/actionList'; +import { TerminalQuickFix, TerminalQuickFixType, toMenuItems } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems'; const quickFixTelemetryTitle = 'terminal/quick-fix'; type QuickFixResultTelemetryEvent = { @@ -80,7 +78,6 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, - @ICommandService private readonly _commandService: ICommandService, @IActionWidgetService private readonly _actionWidgetService: IActionWidgetService ) { super(); @@ -233,7 +230,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, }; // TODO: What's documentation do? Need a vscode command? const documentation = fixes.map(f => { return { id: f.id, title: f.label, tooltip: f.tooltip }; }); - const actions = fixes.map(f => new TerminalQuickFix(f, f.label)); + const actions = fixes.map(f => new TerminalQuickFix(f, f.class || TerminalQuickFixType.Command, f.label)); const actionSet = { // TODO: Documentation and actions are separate? documentation, @@ -248,13 +245,9 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, return; } const delegate = { - onSelect: async (fix: TerminalQuickFix, preview?: boolean) => { - if (preview) { - this._commandService.executeCommand(previewSelectedActionCommand); - } else { - fix.action?.run(); - this._actionWidgetService.hide(); - } + onSelect: async (fix: TerminalQuickFix) => { + fix.action?.run(); + this._actionWidgetService.hide(); }, onHide: () => { this._terminal?.focus(); @@ -303,12 +296,12 @@ export function getQuickFixesForCommand( let action: IAction | undefined; if ('type' in quickFix) { switch (quickFix.type) { - case 'command': { + case TerminalQuickFixType.Command: { const label = localize('quickFix.command', 'Run: {0}', quickFix.command); action = { id: quickFix.id, label, - class: undefined, + class: quickFix.type, enabled: true, run: () => { onDidRequestRerunCommand?.fire({ @@ -322,12 +315,12 @@ export function getQuickFixesForCommand( expectedCommands.push(quickFix.command); break; } - case 'opener': { + case TerminalQuickFixType.Opener: { const label = localize('quickFix.opener', 'Open: {0}', quickFix.uri.toString()); action = { - id: `quickFix.opener`, + id: quickFix.id, label, - class: undefined, + class: quickFix.type, enabled: true, run: () => { openerService.open(quickFix.uri); @@ -418,7 +411,7 @@ export function convertToQuickFixOptions(quickFix: IExtensionTerminalQuickFix): } link = link.replaceAll(varToResolve, value); } - return link ? { type: 'opener', uri: URI.parse(link) } as ITerminalQuickFixOpenerAction : []; + return link ? { type: 'opener', uri: URI.parse(link), id: quickFix.id } as ITerminalQuickFixOpenerAction : []; }, exitStatus: quickFix.exitStatus, source: quickFix.extensionIdentifier diff --git a/src/vs/workbench/contrib/terminal/test/browser/quickFixAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/quickFixAddon.test.ts index e49d75f87da..88eaac4bf71 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/quickFixAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/quickFixAddon.test.ts @@ -254,7 +254,7 @@ suite('QuickFixAddon', () => { Branch 'test22' set up to track remote branch 'test22' from 'origin'. `; const exitCode = 0; const actions = [{ - id: `quickFix.opener`, + id: 'Git Create Pr', enabled: true, label: 'Open: https://github.com/meganrogge/xterm.js/pull/new/test22', tooltip: 'Open: https://github.com/meganrogge/xterm.js/pull/new/test22', From d9a70c4236a8bde4fe7435df3ecdd333c7bc939c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 16 Nov 2022 20:07:56 -0800 Subject: [PATCH 26/67] Wait for disconnect response before terminating DA (#166137) Fix #165138 --- src/vs/workbench/contrib/debug/browser/rawDebugSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 646cf34625b..49b0b302764 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -569,7 +569,7 @@ export class RawDebugSession implements IDisposable { args.suspendDebuggee = suspendDebuggee; } - this.send('disconnect', args, undefined, 2000); + await this.send('disconnect', args, undefined, 2000); } catch (e) { // Catch the potential 'disconnect' error - no need to show it to the user since the adapter is shutting down } finally { From e10988763497891e1b858c8d47daa39f54323447 Mon Sep 17 00:00:00 2001 From: Han Date: Thu, 17 Nov 2022 15:53:56 +0800 Subject: [PATCH 27/67] Adapter css variables (#166279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * move css variables from markersViewActions.ts to markersViewActions.css * move css variables from testingOutputPeek.ts to testingOutputPeek.css * move css variables from linkedEditing.ts to linkedEditing.css * move css variables from interactiveEditor.ts to interactiveEditor.css * add editor linked editing color to color registry * 💄 * add css import Co-authored-by: Martin Aeschlimann --- .../linkedEditing/browser/linkedEditing.css | 9 +++++ .../linkedEditing/browser/linkedEditing.ts | 10 ++---- .../interactive/browser/interactiveEditor.css | 21 +++++++++++ .../interactive/browser/interactiveEditor.ts | 29 +++------------ .../markers/browser/markersViewActions.css | 10 ++++++ .../markers/browser/markersViewActions.ts | 17 +-------- .../testing/browser/testingOutputPeek.css | 22 ++++++++++++ .../testing/browser/testingOutputPeek.ts | 35 ++----------------- 8 files changed, 72 insertions(+), 81 deletions(-) create mode 100644 src/vs/editor/contrib/linkedEditing/browser/linkedEditing.css create mode 100644 src/vs/workbench/contrib/interactive/browser/interactiveEditor.css create mode 100644 src/vs/workbench/contrib/markers/browser/markersViewActions.css create mode 100644 src/vs/workbench/contrib/testing/browser/testingOutputPeek.css diff --git a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.css b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.css new file mode 100644 index 00000000000..5e2ecdb5400 --- /dev/null +++ b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.css @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .linked-editing-decoration { + background-color: var(--vscode-editor-linkedEditingBackground); + border-left-color: var(--vscode-editor-linkedEditingBackground); +} diff --git a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts index 4cfa30dac1c..c5e20edfe5f 100644 --- a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts @@ -28,13 +28,13 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import * as nls from 'vs/nls'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { registerColor } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { StopWatch } from 'vs/base/common/stopwatch'; +import 'vs/css!./linkedEditing'; export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey('LinkedEditingInputVisible', false); @@ -461,12 +461,6 @@ function getLinkedEditingRanges(providers: LanguageFeatureRegistry { - const editorLinkedEditingBackgroundColor = theme.getColor(editorLinkedEditingBackground); - if (editorLinkedEditingBackgroundColor) { - collector.addRule(`.monaco-editor .${DECORATION_CLASS_NAME} { background: ${editorLinkedEditingBackgroundColor}; border-left-color: ${editorLinkedEditingBackgroundColor}; }`); - } -}); registerModelAndPositionCommand('_executeLinkedEditingProvider', (_accessor, model, position) => { const { linkedEditingRangeProvider } = _accessor.get(ILanguageFeaturesService); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.css b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.css new file mode 100644 index 00000000000..491aa3e8c17 --- /dev/null +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.css @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.interactive-editor .input-cell-container:focus-within .input-editor-container .monaco-editor { + outline: solid 1px var(--vscode-notebook-focusedCellBorder); +} + +.interactive-editor .input-cell-container .input-editor-container .monaco-editor { + outline: solid 1px var(--vscode-notebook-inactiveFocusedCellBorder); +} + +.interactive-editor .input-cell-container .input-focus-indicator { + top: 8px; +} + +.interactive-editor .input-cell-container .monaco-editor-background, +.interactive-editor .input-cell-container .margin-view-overlays { + background-color: var(--vscode-notebook-cellEditorBackground, --vscode-editor-background); +} diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 86efcc7fdc3..cae59e4ac5a 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -16,8 +16,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { editorBackground, editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -25,7 +25,7 @@ import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; -import { cellEditorBackground, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExecutionStateCellStatusBarContrib, TimerCellStatusBarContrib } from 'vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; @@ -60,6 +60,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { isEqual } from 'vs/base/common/resources'; import { NotebookFindContrib } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; import { INTERACTIVE_WINDOW_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import 'vs/css!./interactiveEditor'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -683,25 +684,3 @@ export class InteractiveEditor extends EditorPane { }; } } - -registerThemingParticipant((theme, collector) => { - collector.addRule(` - .interactive-editor .input-cell-container:focus-within .input-editor-container .monaco-editor { - outline: solid 1px var(--vscode-notebook-focusedCellBorder); - } - .interactive-editor .input-cell-container .input-editor-container .monaco-editor { - outline: solid 1px var(--vscode-notebook-inactiveFocusedCellBorder); - } - .interactive-editor .input-cell-container .input-focus-indicator { - top: ${INPUT_CELL_VERTICAL_PADDING}px; - } - `); - - const editorBackgroundColor = theme.getColor(cellEditorBackground) ?? theme.getColor(editorBackground); - if (editorBackgroundColor) { - collector.addRule(`.interactive-editor .input-cell-container .monaco-editor-background, - .interactive-editor .input-cell-container .margin-view-overlays { - background: ${editorBackgroundColor}; - }`); - } -}); diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.css b/src/vs/workbench/contrib/markers/browser/markersViewActions.css new file mode 100644 index 00000000000..83ca947571d --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.css @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { + border-color: var(--vscode-inputOption-activeBorder); + color: var(--vscode-inputOption-activeForeground); + background-color: var(--vscode-inputOption-activeBackground); +} diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 65d23126b72..caf421e2931 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -7,15 +7,14 @@ import * as DOM from 'vs/base/browser/dom'; import { Action, IAction } from 'vs/base/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; -import { registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { Codicon } from 'vs/base/common/codicons'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { MarkersContextKeys } from 'vs/workbench/contrib/markers/common/markers'; +import 'vs/css!./markersViewActions'; export interface IMarkersFiltersChangeEvent { excludedFiles?: boolean; @@ -174,17 +173,3 @@ export class QuickFixActionViewItem extends ActionViewItem { } } -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { - const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); - if (inputActiveOptionBorderColor) { - collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { border-color: ${inputActiveOptionBorderColor}; }`); - } - const inputActiveOptionForegroundColor = theme.getColor(inputActiveOptionForeground); - if (inputActiveOptionForegroundColor) { - collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { color: ${inputActiveOptionForegroundColor}; }`); - } - const inputActiveOptionBackgroundColor = theme.getColor(inputActiveOptionBackground); - if (inputActiveOptionBackgroundColor) { - collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { background-color: ${inputActiveOptionBackgroundColor}; }`); - } -}); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.css b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.css new file mode 100644 index 00000000000..0daecae72f2 --- /dev/null +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .test-output-peek .test-output-peek-tree { + background-color: var(--vscode-peekViewResult-background); + color: var(--vscode-peekViewResult-lineForeground); +} + +.monaco-editor .test-output-peek .test-output-peek-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { + background-color: var(--vscode-peekViewResult-selectionBackground); + color: var(--vscode-peekViewResult-selectionForeground) !important; +} + +.monaco-editor .test-output-peek .test-output-peek-message-container a { + color: var(--vscode-textLink-foreground); +} + +.monaco-editor .test-output-peek .test-output-peek-message-container a :hover { + color: var(--vscode-textLink-activeForeground); +} diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 29009105d1a..c918579d28b 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -40,7 +40,7 @@ import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; -import { getOuterEditor, IPeekViewService, peekViewResultsBackground, peekViewResultsMatchForeground, peekViewResultsSelectionBackground, peekViewResultsSelectionForeground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from 'vs/editor/contrib/peekView/browser/peekView'; +import { getOuterEditor, IPeekViewService, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from 'vs/editor/contrib/peekView/browser/peekView'; import { localize } from 'vs/nls'; import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -53,8 +53,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { flatTestItemDelimiter } from 'vs/workbench/contrib/testing/browser/explorerProjections/display'; @@ -77,6 +76,7 @@ import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/test import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import 'vs/css!./testingOutputPeek'; class TestDto { public readonly test: ITestItem; @@ -1621,35 +1621,6 @@ class TreeActionsProvider { } } -registerThemingParticipant((theme, collector) => { - const resultsBackground = theme.getColor(peekViewResultsBackground); - if (resultsBackground) { - collector.addRule(`.monaco-editor .test-output-peek .test-output-peek-tree { background-color: ${resultsBackground}; }`); - } - const resultsMatchForeground = theme.getColor(peekViewResultsMatchForeground); - if (resultsMatchForeground) { - collector.addRule(`.monaco-editor .test-output-peek .test-output-peek-tree { color: ${resultsMatchForeground}; }`); - } - const resultsSelectedBackground = theme.getColor(peekViewResultsSelectionBackground); - if (resultsSelectedBackground) { - collector.addRule(`.monaco-editor .test-output-peek .test-output-peek-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`); - } - const resultsSelectedForeground = theme.getColor(peekViewResultsSelectionForeground); - if (resultsSelectedForeground) { - collector.addRule(`.monaco-editor .test-output-peek .test-output-peek-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`); - } - - const textLinkForegroundColor = theme.getColor(textLinkForeground); - if (textLinkForegroundColor) { - collector.addRule(`.monaco-editor .test-output-peek .test-output-peek-message-container a { color: ${textLinkForegroundColor}; }`); - } - - const textLinkActiveForegroundColor = theme.getColor(textLinkActiveForeground); - if (textLinkActiveForegroundColor) { - collector.addRule(`.monaco-editor .test-output-peek .test-output-peek-message-container a :hover { color: ${textLinkActiveForegroundColor}; }`); - } -}); - const navWhen = ContextKeyExpr.and( EditorContextKeys.focus, TestingContextKeys.isPeekVisible, From 562b3dbd54ccea9d9bb470862ce6f5916d561ae9 Mon Sep 17 00:00:00 2001 From: Daniel Martinez Olivas Date: Thu, 17 Nov 2022 02:50:56 -0600 Subject: [PATCH 28/67] moves css rules (#166532) --- src/vs/platform/opener/browser/link.css | 13 +++++++++++++ src/vs/platform/opener/browser/link.ts | 14 -------------- 2 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 src/vs/platform/opener/browser/link.css diff --git a/src/vs/platform/opener/browser/link.css b/src/vs/platform/opener/browser/link.css new file mode 100644 index 00000000000..f55549478e8 --- /dev/null +++ b/src/vs/platform/opener/browser/link.css @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-link { + color: var(--vscode-textLink-foreground); +} + +.monaco-link:hover { + color: var(--vscode-textLink-activeForeground); +} + diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index 86ad9f5f3ba..85d2bc75a7d 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -11,8 +11,6 @@ import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export interface ILinkDescriptor { readonly label: string | HTMLElement; @@ -119,15 +117,3 @@ export class Link extends Disposable { this.enabled = true; } } - -registerThemingParticipant((theme, collector) => { - const textLinkForegroundColor = theme.getColor(textLinkForeground); - if (textLinkForegroundColor) { - collector.addRule(`.monaco-link { color: ${textLinkForegroundColor}; }`); - } - - const textLinkActiveForegroundColor = theme.getColor(textLinkActiveForeground); - if (textLinkActiveForegroundColor) { - collector.addRule(`.monaco-link:hover { color: ${textLinkActiveForegroundColor}; }`); - } -}); From f675b2bfab018ce44e5db50e6f5d676cf86d7bca Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 17 Nov 2022 09:58:12 +0100 Subject: [PATCH 29/67] remove some any casts that aren't needed (anymore) (#166557) --- src/vs/base/common/types.ts | 8 ++++++++ src/vs/editor/common/core/wordHelper.ts | 2 +- src/vs/platform/remote/common/remoteAuthorityResolver.ts | 4 +--- src/vs/workbench/api/common/extHostTypes.ts | 8 ++------ src/vs/workbench/api/common/extensionHostMain.ts | 7 ++++--- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 4 +--- .../comments/browser/commentsEditorContribution.ts | 2 +- .../remote/common/remoteAgentEnvironmentChannel.ts | 3 ++- src/vs/workbench/services/themes/common/plistParser.ts | 4 ++-- 9 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 08063ae38d1..c35da047d1f 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -224,3 +224,11 @@ export type AddFirstParameterToFunctions; */ export type AtLeastOne }> = Partial & U[keyof U]; + + +/** + * A type that removed readonly-less from all properties of `T` + */ +export type Mutable = { + -readonly [P in keyof T]: T[P] +}; diff --git a/src/vs/editor/common/core/wordHelper.ts b/src/vs/editor/common/core/wordHelper.ts index cdb2d4b8bfb..862d94ccb06 100644 --- a/src/vs/editor/common/core/wordHelper.ts +++ b/src/vs/editor/common/core/wordHelper.ts @@ -61,7 +61,7 @@ export function ensureValidWordDefinition(wordDefinition?: RegExp | null): RegEx if (wordDefinition.multiline) { flags += 'm'; } - if ((wordDefinition as any).unicode) { + if (wordDefinition.unicode) { flags += 'u'; } result = new RegExp(wordDefinition.source, flags); diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 87ec7fead5e..607f53c8c9e 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -97,9 +97,7 @@ export class RemoteAuthorityResolverError extends ErrorNoTelemetry { // workaround when extending builtin objects and when compiling to ES5, see: // https://github.com/microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - if (typeof (Object).setPrototypeOf === 'function') { - (Object).setPrototypeOf(this, RemoteAuthorityResolverError.prototype); - } + Object.setPrototypeOf(this, RemoteAuthorityResolverError.prototype); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 858a37b9a8e..66286db4e95 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -515,9 +515,7 @@ export class RemoteAuthorityResolverError extends Error { // workaround when extending builtin objects and when compiling to ES5, see: // https://github.com/microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - if (typeof (Object).setPrototypeOf === 'function') { - (Object).setPrototypeOf(this, RemoteAuthorityResolverError.prototype); - } + Object.setPrototypeOf(this, RemoteAuthorityResolverError.prototype); } } @@ -2954,9 +2952,7 @@ export class FileSystemError extends Error { // workaround when extending builtin objects and when compiling to ES5, see: // https://github.com/microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - if (typeof (Object).setPrototypeOf === 'function') { - (Object).setPrototypeOf(this, FileSystemError.prototype); - } + Object.setPrototypeOf(this, FileSystemError.prototype); if (typeof Error.captureStackTrace === 'function' && typeof terminator === 'function') { // nice stack traces diff --git a/src/vs/workbench/api/common/extensionHostMain.ts b/src/vs/workbench/api/common/extensionHostMain.ts index 88bc7909828..623777165ba 100644 --- a/src/vs/workbench/api/common/extensionHostMain.ts +++ b/src/vs/workbench/api/common/extensionHostMain.ts @@ -11,7 +11,7 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtensionHostInitData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -22,6 +22,7 @@ import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/e import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import { Mutable } from 'vs/base/common/types'; export interface IExitFn { (code?: number): any; @@ -163,11 +164,11 @@ export class ExtensionHostMain { private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData { initData.allExtensions.forEach((ext) => { - (ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); + (>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); const browserNlsBundleUris: { [language: string]: URI } = {}; if (ext.browserNlsBundleUris) { Object.keys(ext.browserNlsBundleUris).forEach(lang => browserNlsBundleUris[lang] = URI.revive(rpcProtocol.transformIncomingURIs(ext.browserNlsBundleUris![lang]))); - (ext).browserNlsBundleUris = browserNlsBundleUris; + (>ext).browserNlsBundleUris = browserNlsBundleUris; } }); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 8e0108c20df..ac7a2b01767 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -38,6 +38,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Mutable } from 'vs/base/common/types'; const enum State { Data = 'data', @@ -314,9 +315,6 @@ export class BulkEditPane extends ViewPane { } private async _openElementAsEditor(e: IOpenEvent): Promise { - type Mutable = { - -readonly [P in keyof T]: T[P] - }; const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index da5c81f2b62..021e56c0edb 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -698,7 +698,7 @@ export class CommentController implements IEditorContribution { return; } - const matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); + const matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); if (matchedNewCommentThreadZones.length) { matchedNewCommentThreadZones[0].update(thread); diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 35bfae00a4d..a50a0bdab19 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -12,6 +12,7 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn 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'; export interface IGetEnvironmentDataArguments { remoteAuthority: string; @@ -118,7 +119,7 @@ export class RemoteExtensionEnvironmentChannelClient { const extension = await channel.call('scanSingleExtension', args); if (extension) { - (extension).extensionLocation = URI.revive(extension.extensionLocation); + (>extension).extensionLocation = URI.revive(extension.extensionLocation); } return extension; } diff --git a/src/vs/workbench/services/themes/common/plistParser.ts b/src/vs/workbench/services/themes/common/plistParser.ts index d51ce513f66..a9c83450b0e 100644 --- a/src/vs/workbench/services/themes/common/plistParser.ts +++ b/src/vs/workbench/services/themes/common/plistParser.ts @@ -326,9 +326,9 @@ function _parse(content: string, filename: string | null, locationKeyName: strin function escapeVal(str: string): string { return str.replace(/&#([0-9]+);/g, function (_: string, m0: string) { - return (String).fromCodePoint(parseInt(m0, 10)); + return String.fromCodePoint(parseInt(m0, 10)); }).replace(/&#x([0-9a-f]+);/g, function (_: string, m0: string) { - return (String).fromCodePoint(parseInt(m0, 16)); + return String.fromCodePoint(parseInt(m0, 16)); }).replace(/&|<|>|"|'/g, function (_: string) { switch (_) { case '&': return '&'; From 4bfa86cdbfd0fe07bc7436d7cc99a6ab0bf17e76 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 17 Nov 2022 09:51:43 +0000 Subject: [PATCH 30/67] =?UTF-8?q?=E2=9D=8C=20Remove=20unnecessary=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- .../workbench/browser/parts/editor/breadcrumbsControl.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 99565340c5b..3dcc28fbd14 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -565,14 +565,6 @@ registerAction2(class FocusAndSelectBreadcrumbs extends Action2 { } }); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'breadcrumbs.focus', - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Semicolon, - when: BreadcrumbsControl.CK_BreadcrumbsPossible, - handler: accessor => focusAndSelectHandler(accessor, false) -}); - registerAction2(class FocusBreadcrumbs extends Action2 { constructor() { super({ From f6c8436c21bd83166fc798e6fb8eb222b5b6686f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 17 Nov 2022 09:53:13 +0000 Subject: [PATCH 31/67] =?UTF-8?q?=F0=9F=92=84=20Include=20`breadcrumbs.foc?= =?UTF-8?q?usAndSelect`=20in=20command=20palette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 3dcc28fbd14..68c3d487a41 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -557,7 +557,8 @@ registerAction2(class FocusAndSelectBreadcrumbs extends Action2 { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Period, when: BreadcrumbsControl.CK_BreadcrumbsPossible, - } + }, + f1: true }); } run(accessor: ServicesAccessor, ...args: any[]): void { From aaac6f9a99a221503dbbc7aec2d174b32b0ef99a Mon Sep 17 00:00:00 2001 From: Marcus Revaj Date: Thu, 17 Nov 2022 11:16:06 +0100 Subject: [PATCH 32/67] Add additional actions to CommentThread (#162750) * # Add additional actions to CommentThread * Add in forgotten property * # missing declaration * type change * Fix merge conflict errors * # add proposed changes + fix styling * # Allow "secondary action only" buttons * # Add dropdown button styling fixes * # add default button styling * # add better styling to the dropdown button * Remove duplicate css rule Co-authored-by: Alex Ross --- src/vs/base/browser/ui/button/button.css | 5 + src/vs/base/browser/ui/button/button.ts | 5 +- src/vs/base/common/marshallingIds.ts | 1 + src/vs/platform/actions/common/actions.ts | 1 + .../workbench/api/common/extHostComments.ts | 6 +- .../comments/browser/commentFormActions.ts | 37 +++++-- .../contrib/comments/browser/commentMenus.ts | 4 + .../browser/commentThreadAdditionalActions.ts | 97 +++++++++++++++++++ .../comments/browser/commentThreadWidget.ts | 16 +++ .../contrib/comments/browser/media/review.css | 38 +++++++- .../contrib/scm/browser/media/scm.css | 5 - .../actions/common/menusExtensionPoint.ts | 7 ++ .../common/extensionsApiProposals.ts | 1 + ...ed.contribCommentThreadAdditionalMenu.d.ts | 8 ++ 14 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts create mode 100644 src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 36beeb00478..dff0019cfee 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -69,6 +69,11 @@ .monaco-button-dropdown > .monaco-button.monaco-dropdown-button { border-left-width: 0 !important; + border-radius: 0 2px 2px 0; +} + +.monaco-button-dropdown > .monaco-button.monaco-text-button { + border-radius: 2px 0 0 2px; } .monaco-description-button { diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 0d28e607144..45d213b4781 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -257,9 +257,10 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.separatorContainer.style.borderTop = '1px solid ' + border; this.separatorContainer.style.borderBottom = '1px solid ' + border; } - this.separatorContainer.style.backgroundColor = options.buttonBackground ?? ''; - this.separator.style.backgroundColor = options.buttonSeparator ?? ''; + const buttonBackground = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground; + this.separatorContainer.style.backgroundColor = buttonBackground ?? ''; + this.separator.style.backgroundColor = options.buttonSeparator ?? ''; this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); diff --git a/src/vs/base/common/marshallingIds.ts b/src/vs/base/common/marshallingIds.ts index 4387c42cc67..abd7698ed92 100644 --- a/src/vs/base/common/marshallingIds.ts +++ b/src/vs/base/common/marshallingIds.ts @@ -12,6 +12,7 @@ export const enum MarshalledId { ScmProvider, CommentController, CommentThread, + CommentThreadInstance, CommentThreadReply, CommentNode, CommentThreadNode, diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index a39a46fd6c5..762b9754744 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -128,6 +128,7 @@ export class MenuId { static readonly ViewTitleContext = new MenuId('ViewTitleContext'); static readonly CommentThreadTitle = new MenuId('CommentThreadTitle'); static readonly CommentThreadActions = new MenuId('CommentThreadActions'); + static readonly CommentThreadAdditionalActions = new MenuId('CommentThreadAdditionalActions'); static readonly CommentThreadTitleContext = new MenuId('CommentThreadTitleContext'); static readonly CommentThreadCommentContext = new MenuId('CommentThreadCommentContext'); static readonly CommentTitle = new MenuId('CommentTitle'); diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index e66c60290f9..2e2f0e0835c 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -66,7 +66,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo } return commentThread.value; - } else if (arg && arg.$mid === MarshalledId.CommentThreadReply) { + } else if (arg && (arg.$mid === MarshalledId.CommentThreadReply || arg.$mid === MarshalledId.CommentThreadInstance)) { const commentController = this._commentControllers.get(arg.thread.commentControlHandle); if (!commentController) { @@ -79,6 +79,10 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return arg; } + if (arg.$mid === MarshalledId.CommentThreadInstance) { + return commentThread.value; + } + return { thread: commentThread.value, text: arg.text diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index 8e5bed5afac..e4fb1b79e31 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Button } from 'vs/base/browser/ui/button/button'; +import { Button, ButtonWithDropdown, IButton } from 'vs/base/browser/ui/button/button'; import { IAction } from 'vs/base/common/actions'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IMenu } from 'vs/platform/actions/common/actions'; +import { IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; export class CommentFormActions implements IDisposable { private _buttonElements: HTMLElement[] = []; @@ -17,29 +18,49 @@ export class CommentFormActions implements IDisposable { constructor( private container: HTMLElement, private actionHandler: (action: IAction) => void, + private contextMenuService?: IContextMenuService ) { } - setActions(menu: IMenu) { + setActions(menu: IMenu, hasOnlySecondaryActions: boolean = false) { this._toDispose.clear(); this._buttonElements.forEach(b => b.remove()); const groups = menu.getActions({ shouldForwardArgs: true }); - let isPrimary: boolean = true; + let isPrimary: boolean = !hasOnlySecondaryActions; for (const group of groups) { const [, actions] = group; this._actions = actions; for (const action of actions) { - const button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles }); + const submenuAction = action as SubmenuItemAction; + + // Use the first action from the submenu as the primary button. + const appliedAction: IAction = submenuAction.actions?.length > 0 ? submenuAction.actions[0] : action; + let button: IButton | undefined; + + // Use dropdown only if submenu contains more than 1 action. + if (submenuAction.actions?.length > 1 && this.contextMenuService) { + button = new ButtonWithDropdown(this.container, + { + contextMenuProvider: this.contextMenuService, + actions: submenuAction.actions.slice(1), + addPrimaryActionToDropdown: false, + secondary: !isPrimary, + ...defaultButtonStyles + }); + } else { + button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles }); + } + isPrimary = false; this._buttonElements.push(button.element); this._toDispose.add(button); - this._toDispose.add(button.onDidClick(() => this.actionHandler(action))); + this._toDispose.add(button.onDidClick(() => this.actionHandler(appliedAction))); - button.enabled = action.enabled; - button.label = action.label; + button.enabled = appliedAction.enabled; + button.label = appliedAction.label; } } } diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index dae9f44956f..479e5156b12 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -23,6 +23,10 @@ export class CommentMenus implements IDisposable { return this.getMenu(MenuId.CommentThreadActions, contextKeyService); } + getCommentThreadAdditionalActions(contextKeyService: IContextKeyService): IMenu { + return this.getMenu(MenuId.CommentThreadAdditionalActions, contextKeyService); + } + getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IMenu { return this.getMenu(MenuId.CommentTitle, contextKeyService); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts b/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts new file mode 100644 index 00000000000..0e3a9870d29 --- /dev/null +++ b/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; + +import { IAction } from 'vs/base/common/actions'; +import { IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { IRange } from 'vs/editor/common/core/range'; +import * as languages from 'vs/editor/common/languages'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; +import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; + +export class CommentThreadAdditionalActions extends Disposable { + private _container: HTMLElement | null; + private _buttonBar: HTMLElement | null; + private _commentFormActions!: CommentFormActions; + + constructor( + container: HTMLElement, + private _commentThread: languages.CommentThread, + private _contextKeyService: IContextKeyService, + private _commentMenus: CommentMenus, + private _actionRunDelegate: (() => void) | null, + @IContextMenuService private contextMenuService: IContextMenuService, + ) { + super(); + + this._container = dom.append(container, dom.$('.comment-additional-actions')); + dom.append(this._container, dom.$('.section-separator')); + + this._buttonBar = dom.append(this._container, dom.$('.button-bar')); + this._createAdditionalActions(this._buttonBar); + } + + private _showMenu() { + this._container?.classList.remove('hidden'); + } + + private _hideMenu() { + this._container?.classList.add('hidden'); + } + + private _enableDisableMenu(menu: IMenu) { + const groups = menu.getActions({ shouldForwardArgs: true }); + + // Show the menu if at least one action is enabled. + for (const group of groups) { + const [, actions] = group; + for (const action of actions) { + if (action.enabled) { + this._showMenu(); + return; + } + + for (const subAction of (action as SubmenuItemAction).actions ?? []) { + if (subAction.enabled) { + this._showMenu(); + return; + } + } + } + } + + this._hideMenu(); + } + + + private _createAdditionalActions(container: HTMLElement) { + const menu = this._commentMenus.getCommentThreadAdditionalActions(this._contextKeyService); + this._register(menu); + this._register(menu.onDidChange(() => { + this._commentFormActions.setActions(menu); + this._enableDisableMenu(menu); + })); + + this._commentFormActions = new CommentFormActions(container, async (action: IAction) => { + this._actionRunDelegate?.(); + + action.run({ + thread: this._commentThread, + $mid: MarshalledId.CommentThreadInstance + }); + + }, this.contextMenuService); + + this._register(this._commentFormActions); + this._commentFormActions.setActions(menu, /*hasOnlySecondaryActions*/ true); + this._enableDisableMenu(menu); + } +} diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index d7af5c13258..8c07cd661fe 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -17,6 +17,7 @@ import { CommentReply } from 'vs/workbench/contrib/comments/browser/commentReply import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { CommentThreadBody } from 'vs/workbench/contrib/comments/browser/commentThreadBody'; import { CommentThreadHeader } from 'vs/workbench/contrib/comments/browser/commentThreadHeader'; +import { CommentThreadAdditionalActions } from 'vs/workbench/contrib/comments/browser/commentThreadAdditionalActions'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; @@ -35,6 +36,7 @@ export class CommentThreadWidget extends private _header!: CommentThreadHeader; private _body!: CommentThreadBody; private _commentReply?: CommentReply; + private _additionalActions?: CommentThreadAdditionalActions; private _commentMenus: CommentMenus; private _commentThreadDisposables: IDisposable[] = []; private _threadIsEmpty: IContextKey; @@ -177,6 +179,7 @@ export class CommentThreadWidget extends if (this._commentThread.canReply) { this._createCommentForm(); } + this._createAdditionalActions(); this._register(this._body.onDidResize(dimension => { this._refresh(dimension); @@ -239,6 +242,19 @@ export class CommentThreadWidget extends this._register(this._commentReply); } + private _createAdditionalActions() { + this._additionalActions = this._scopedInstatiationService.createInstance( + CommentThreadAdditionalActions, + this._body.container, + this._commentThread, + this._contextKeyService, + this._commentMenus, + this._containerDelegate.actionRunner, + ); + + this._register(this._additionalActions); + } + getCommentCoords(commentUniqueId: number) { return this._body.getCommentCoords(commentUniqueId); } diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index d26c07a67c4..ef33e562093 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -246,6 +246,42 @@ word-wrap: break-word; } + +.review-widget .body .comment-additional-actions { + margin: 10px 20px; +} + +.review-widget .body .comment-additional-actions .section-separator { + border-top: 1px solid var(--vscode-menu-separatorBackground); + margin: 14px 0; +} + +.review-widget .body .comment-additional-actions .button-bar { + display: flex; + white-space: nowrap; +} + +.review-widget .body .comment-additional-actions .monaco-button, +.review-widget .body .comment-additional-actions .monaco-text-button, +.review-widget .body .comment-additional-actions .monaco-button-dropdown { + display: flex; + width: auto; +} + +.review-widget .body .comment-additional-actions .button-bar>.monaco-text-button, +.review-widget .body .comment-additional-actions .button-bar>.monaco-button-dropdown { + margin: 0 10px 0 0; +} + +.review-widget .body .comment-additional-actions .button-bar .monaco-text-button { + padding: 4px 10px; +} + + +.review-widget .body .comment-additional-actions .codicon-drop-down-button { + align-items: center; +} + .review-widget .body .comment-form.expand .review-thread-reply-button { display: none; } @@ -295,7 +331,7 @@ .review-widget .body .comment-form .form-actions, .review-widget .body .edit-container .form-actions { overflow: auto; - padding: 10px 0; + margin: 10px 0; } .review-widget .body .edit-textarea { diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 6a08158a58a..37a9d5a75e2 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -287,13 +287,8 @@ padding: 0 4px; } -/* split commit button */ -.scm-view .button-container > .monaco-button-dropdown > .monaco-dropdown-button.codicon-drop-down-button { - border-radius: 0 2px 2px 0; -} .scm-view .button-container > .monaco-button-dropdown > .monaco-button.monaco-text-button { - border-radius: 2px 0 0 2px; min-width: 0; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 80a2cb98aa2..cc2ba477dfe 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -159,6 +159,13 @@ const apiMenus: IAPIMenu[] = [ description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"), supportsSubmenus: false }, + { + key: 'comments/commentThread/additionalActions', + id: MenuId.CommentThreadAdditionalActions, + description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"), + supportsSubmenus: true, + proposed: 'contribCommentThreadAdditionalMenu' + }, { key: 'comments/commentThread/title/context', id: MenuId.CommentThreadTitleContext, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index d9336a794c4..88ee287f9c5 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -10,6 +10,7 @@ export const allApiProposals = Object.freeze({ codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentsResolvedState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts', contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', + contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts b/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts new file mode 100644 index 00000000000..2e71d90a25a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder for comment thread additional menus + +// https://github.com/microsoft/vscode/issues/163281 From 0824db3bad99b3e463a01d10417d7b8b57cea193 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 12:09:14 +0100 Subject: [PATCH 33/67] * add global for node_modules access * remove most usages of require.__$__nodeRequire * stop using require.nodeRequire --- src/bootstrap-fork.js | 11 ++++++++ src/bootstrap-window.js | 10 +++++++ src/main.js | 11 ++++++++ src/typings/vscode-globals.d.ts | 28 +++++++++++++++++++ src/vs/base/common/performance.js | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 8 +++--- .../test/node/nativeModules.test.ts | 4 +-- .../node/remoteExtensionHostAgentServer.ts | 2 +- .../api/node/extHostExtensionService.ts | 2 +- .../api/node/extensionHostProcess.ts | 4 +-- src/vs/workbench/api/node/proxyResolver.ts | 2 +- test/unit/electron/renderer.js | 10 +++++++ 12 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 src/typings/vscode-globals.d.ts diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index b9d66d444a8..9d760ddf101 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -37,6 +37,17 @@ if (process.env['VSCODE_PARENT_PID']) { terminateWhenParentTerminates(); } +// VSCODE_GLOBALS: node_modules +globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { + get(target, mod) { + if (!target[mod] && typeof mod === 'string') { + target[mod] = require(mod); + } + return target[mod]; + } +}); + + // Load AMD entry point require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 8bdd75c63c0..f8b895c2d77 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -112,6 +112,16 @@ window['MonacoEnvironment'] = {}; + // VSCODE_GLOBALS: node_modules + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { + get(target, mod) { + if (!target[mod] && typeof mod === 'string') { + target[mod] = (require.__$__nodeRequire ?? require)(mod); + } + return target[mod]; + } + }); + const loaderConfig = { baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, 'vs/nls': nlsConfig, diff --git a/src/main.js b/src/main.js index 63f8c5679f9..4c43e0923b7 100644 --- a/src/main.js +++ b/src/main.js @@ -141,6 +141,16 @@ function startup(codeCachePath, nlsConfig) { process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || ''; + // VSCODE_GLOBALS: node_modules + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { + get(target, mod) { + if (!target[mod] && typeof mod === 'string') { + target[mod] = require(mod); + } + return target[mod]; + } + }); + // Load main in AMD perf.mark('code/willLoadMainBundle'); require('./bootstrap-amd').load('vs/code/electron-main/main', () => { @@ -318,6 +328,7 @@ function getArgvConfigPath() { dataFolderName = `${dataFolderName}-dev`; } + // @ts-ignore return path.join(os.homedir(), dataFolderName, 'argv.json'); } diff --git a/src/typings/vscode-globals.d.ts b/src/typings/vscode-globals.d.ts new file mode 100644 index 00000000000..f8d32c405bf --- /dev/null +++ b/src/typings/vscode-globals.d.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare global { + + /** + * @deprecated node modules that are in used in a context that + * shouldn't have access to node_modules (node-free renderer or + * shared process) + */ + var _VSCODE_NODE_MODULES: { + crypto: typeof import('crypto'); + zlib: typeof import('zlib'); + net: typeof import('net'); + os: typeof import('os'); + module: typeof import('module'); + ['native-watchdog']: typeof import('native-watchdog') + perf_hooks: typeof import('perf_hooks'); + + ['vsda']: any + ['vscode-encrypt']: any + } +} + +// fake export to make global work +export { } diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index 15eab308bf2..92e261d898a 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -78,7 +78,7 @@ } else if (typeof process === 'object') { // node.js: use the normal polyfill but add the timeOrigin // from the node perf_hooks API as very first mark - const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin); + const timeOrigin = Math.round((require.__$__nodeRequire || require)('perf_hooks').performance.timeOrigin); return _definePolyfillMarks(timeOrigin); } else { diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 810ba41d106..455caa3efeb 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -20,10 +20,10 @@ import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEv // TODO@bpasero remove me once electron utility process has landed function getNodeDependencies() { return { - crypto: (require.__$__nodeRequire('crypto') as any) as typeof import('crypto'), - zlib: (require.__$__nodeRequire('zlib') as any) as typeof import('zlib'), - net: (require.__$__nodeRequire('net') as any) as typeof import('net'), - os: (require.__$__nodeRequire('os') as any) as typeof import('os') + crypto: globalThis._VSCODE_NODE_MODULES.crypto, + zlib: globalThis._VSCODE_NODE_MODULES.zlib, + net: globalThis._VSCODE_NODE_MODULES.net, + os: globalThis._VSCODE_NODE_MODULES.os, }; } diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts index 6b7dfad6741..c538d8c3220 100644 --- a/src/vs/platform/environment/test/node/nativeModules.test.ts +++ b/src/vs/platform/environment/test/node/nativeModules.test.ts @@ -58,7 +58,7 @@ flakySuite('Native Modules (all platforms)', () => { test('vscode-encrypt', async () => { try { - const vscodeEncrypt: Encryption = require.__$__nodeRequire('vscode-encrypt'); + const vscodeEncrypt: Encryption = globalThis._VSCODE_NODE_MODULES['vscode-encrypt']; const encrypted = await vscodeEncrypt.encrypt('salt', 'value'); const decrypted = await vscodeEncrypt.decrypt('salt', encrypted); @@ -73,7 +73,7 @@ flakySuite('Native Modules (all platforms)', () => { test('vsda', async () => { try { - const vsda: any = require.__$__nodeRequire('vsda'); + const vsda: any = globalThis._VSCODE_NODE_MODULES['vsda']; const signer = new vsda.signer(); const signed = await signer.sign('value'); assert.ok(typeof signed === 'string', testErrorMessage('vsda')); diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index efc591aab18..ce679e09201 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -727,7 +727,7 @@ export async function createServer(address: string | net.AddressInfo | null, arg const hasVSDA = fs.existsSync(join(FileAccess.asFileUri('').fsPath, '../node_modules/vsda')); if (hasVSDA) { try { - return require.__$__nodeRequire('vsda'); + return globalThis._VSCODE_NODE_MODULES['vsda']; } catch (err) { logService.error(err); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 32e4f223aad..06f91272771 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -22,7 +22,7 @@ class NodeModuleRequireInterceptor extends RequireInterceptor { protected _installInterceptor(): void { const that = this; - const node_module = require.__$__nodeRequire('module'); + const node_module = globalThis._VSCODE_NODE_MODULES.module; const originalLoad = node_module._load; node_module._load = function load(request: string, parent: { filename: string }, isMain: boolean) { request = applyAlternatives(request); diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 5b0030167cd..07289755555 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -81,7 +81,7 @@ const args = minimist(process.argv.slice(2), { // happening we essentially blocklist this module from getting loaded in any // extension by patching the node require() function. (function () { - const Module = require.__$__nodeRequire('module') as any; + const Module = globalThis._VSCODE_NODE_MODULES.module as any; const originalLoad = Module._load; Module._load = function (request: string) { @@ -325,7 +325,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise): Promise { return extensionService.getExtensionPathIndex() .then(extensionPaths => { - const node_module = require.__$__nodeRequire('module'); + const node_module = globalThis._VSCODE_NODE_MODULES.module; const original = node_module._load; node_module._load = function load(request: string, parent: { filename: string }, isMain: boolean) { if (request === 'tls') { diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 47f8fb0a53e..3a7cc2fb66e 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -72,6 +72,16 @@ if (util.inspect && util.inspect['defaultOptions']) { util.inspect['defaultOptions'].customInspect = false; } +// VSCODE_GLOBALS: node_modules +globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { + get(target, mod) { + if (!target[mod] && typeof mod === 'string') { + target[mod] = (require.__$__nodeRequire ?? require)(mod); + } + return target[mod]; + } +}); + const _tests_glob = '**/test/**/*.test.js'; let loader; let _out; From 6ce31d9e1cfec1a264116de9f24a0beb455b87ae Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 17 Nov 2022 12:13:33 +0100 Subject: [PATCH 34/67] Remove split button from comments additional actions (#166568) and add a max number of 4 actions Part of #163281 --- .../comments/browser/commentFormActions.ts | 37 ++++++------------- .../browser/commentThreadAdditionalActions.ts | 7 +--- .../actions/common/menusExtensionPoint.ts | 2 +- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index e4fb1b79e31..bda8197f14e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Button, ButtonWithDropdown, IButton } from 'vs/base/browser/ui/button/button'; +import { Button } from 'vs/base/browser/ui/button/button'; import { IAction } from 'vs/base/common/actions'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu } from 'vs/platform/actions/common/actions'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; export class CommentFormActions implements IDisposable { private _buttonElements: HTMLElement[] = []; @@ -18,7 +17,7 @@ export class CommentFormActions implements IDisposable { constructor( private container: HTMLElement, private actionHandler: (action: IAction) => void, - private contextMenuService?: IContextMenuService + private readonly maxActions?: number ) { } setActions(menu: IMenu, hasOnlySecondaryActions: boolean = false) { @@ -33,34 +32,20 @@ export class CommentFormActions implements IDisposable { this._actions = actions; for (const action of actions) { - const submenuAction = action as SubmenuItemAction; - - // Use the first action from the submenu as the primary button. - const appliedAction: IAction = submenuAction.actions?.length > 0 ? submenuAction.actions[0] : action; - let button: IButton | undefined; - - // Use dropdown only if submenu contains more than 1 action. - if (submenuAction.actions?.length > 1 && this.contextMenuService) { - button = new ButtonWithDropdown(this.container, - { - contextMenuProvider: this.contextMenuService, - actions: submenuAction.actions.slice(1), - addPrimaryActionToDropdown: false, - secondary: !isPrimary, - ...defaultButtonStyles - }); - } else { - button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles }); - } + const button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles }); isPrimary = false; this._buttonElements.push(button.element); this._toDispose.add(button); - this._toDispose.add(button.onDidClick(() => this.actionHandler(appliedAction))); + this._toDispose.add(button.onDidClick(() => this.actionHandler(action))); - button.enabled = appliedAction.enabled; - button.label = appliedAction.label; + button.enabled = action.enabled; + button.label = action.label; + if ((this.maxActions !== undefined) && (this._buttonElements.length >= this.maxActions)) { + console.warn(`An extension has contributed more than the allowable number of actions to a comments menu.`); + return; + } } } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts b/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts index 0e3a9870d29..34366676669 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts @@ -15,7 +15,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; export class CommentThreadAdditionalActions extends Disposable { private _container: HTMLElement | null; @@ -28,7 +27,6 @@ export class CommentThreadAdditionalActions exten private _contextKeyService: IContextKeyService, private _commentMenus: CommentMenus, private _actionRunDelegate: (() => void) | null, - @IContextMenuService private contextMenuService: IContextMenuService, ) { super(); @@ -76,7 +74,7 @@ export class CommentThreadAdditionalActions exten const menu = this._commentMenus.getCommentThreadAdditionalActions(this._contextKeyService); this._register(menu); this._register(menu.onDidChange(() => { - this._commentFormActions.setActions(menu); + this._commentFormActions.setActions(menu, /*hasOnlySecondaryActions*/ true); this._enableDisableMenu(menu); })); @@ -87,8 +85,7 @@ export class CommentThreadAdditionalActions exten thread: this._commentThread, $mid: MarshalledId.CommentThreadInstance }); - - }, this.contextMenuService); + }, 4); this._register(this._commentFormActions); this._commentFormActions.setActions(menu, /*hasOnlySecondaryActions*/ true); diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index cc2ba477dfe..fe81a6da6bc 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -163,7 +163,7 @@ const apiMenus: IAPIMenu[] = [ key: 'comments/commentThread/additionalActions', id: MenuId.CommentThreadAdditionalActions, description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"), - supportsSubmenus: true, + supportsSubmenus: false, proposed: 'contribCommentThreadAdditionalMenu' }, { From 54e36520427eda2e88b3f892ed7131b65b2da7fd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 17 Nov 2022 12:35:36 +0100 Subject: [PATCH 35/67] fix #166350 (#166572) --- src/vs/workbench/contrib/markers/browser/markersView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index f54ff823334..1eea4afe508 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -790,6 +790,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor!, menuId: MenuId.ProblemsPanelContext, + contextKeyService: this.widget.contextKeyService, getActions: () => this.getMenuActions(element), getActionViewItem: (action) => { const keybinding = this.keybindingService.lookupKeybinding(action.id); From 328ed10651b87c930a2d6ccf680e8f6b682ecb4b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 17 Nov 2022 12:49:56 +0100 Subject: [PATCH 36/67] Git drop stash commands should use modal dialog (#166573) Since the warning is initiated through user action, and the user is required to interact with the warning to finish their intended action we should use a modal dialog here. --- extensions/git/src/commands.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6477b95d4a1..67486d7b3b9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3017,6 +3017,7 @@ export class CommandCenter { const yes = l10n.t('Yes'); const result = await window.showWarningMessage( l10n.t('Are you sure you want to drop the stash: {0}?', stash.description), + { modal: true }, yes ); if (result !== yes) { @@ -3041,7 +3042,7 @@ export class CommandCenter { l10n.t('Are you sure you want to drop ALL stashes? There is 1 stash that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.') : l10n.t('Are you sure you want to drop ALL stashes? There are {0} stashes that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.', stashes.length); - const result = await window.showWarningMessage(question, yes); + const result = await window.showWarningMessage(question, { modal: true }, yes); if (result !== yes) { return; } From 9f56e365d7dcca44701dae4dd54924b3016ae360 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 17 Nov 2022 17:50:08 +0330 Subject: [PATCH 37/67] =?UTF-8?q?=F0=9F=8E=81=20Add=20`killOnServerStop`?= =?UTF-8?q?=20to=20debug=20configuration=20(#163779)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎁 Add `killOnServerStop` to schema Signed-off-by: Babak K. Shandiz * 📜 Add description for `killOnServerStop` Signed-off-by: Babak K. Shandiz * 🔨 Stop created debug session on server stop Signed-off-by: Babak K. Shandiz * 🔨 Push kill listeners into another disposable container Signed-off-by: Babak K. Shandiz * 🐛 Prevent leak when new debug session fails to start Signed-off-by: Babak K. Shandiz * 🔨 Use more verbose name for debug session tracker ID Signed-off-by: Babak K. Shandiz Signed-off-by: Babak K. Shandiz --- extensions/debug-server-ready/package.json | 24 +++- .../debug-server-ready/package.nls.json | 1 + .../debug-server-ready/src/extension.ts | 113 ++++++++++++++++-- 3 files changed, 128 insertions(+), 10 deletions(-) diff --git a/extensions/debug-server-ready/package.json b/extensions/debug-server-ready/package.json index 156280396c3..5bfccfb575e 100644 --- a/extensions/debug-server-ready/package.json +++ b/extensions/debug-server-ready/package.json @@ -40,7 +40,8 @@ "additionalProperties": false, "markdownDescription": "%debug.server.ready.serverReadyAction.description%", "default": { - "action": "openExternally" + "action": "openExternally", + "killOnServerStop": false }, "properties": { "action": { @@ -63,6 +64,11 @@ "type": "string", "markdownDescription": "%debug.server.ready.uriFormat.description%", "default": "http://localhost:%s" + }, + "killOnServerStop": { + "type": "boolean", + "markdownDescription": "%debug.server.ready.killOnServerStop.description%", + "default": false } } }, @@ -74,7 +80,8 @@ "action": "debugWithEdge", "pattern": "listening on port ([0-9]+)", "uriFormat": "http://localhost:%s", - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}", + "killOnServerStop": false }, "properties": { "action": { @@ -103,6 +110,11 @@ "type": "string", "markdownDescription": "%debug.server.ready.webRoot.description%", "default": "${workspaceFolder}" + }, + "killOnServerStop": { + "type": "boolean", + "markdownDescription": "%debug.server.ready.killOnServerStop.description%", + "default": false } } }, @@ -112,7 +124,8 @@ "markdownDescription": "%debug.server.ready.serverReadyAction.description%", "default": { "action": "startDebugging", - "name": "" + "name": "", + "killOnServerStop": false }, "required": [ "name" @@ -138,6 +151,11 @@ "type": "string", "markdownDescription": "%debug.server.ready.debugConfigName.description%", "default": "Launch Browser" + }, + "killOnServerStop": { + "type": "boolean", + "markdownDescription": "%debug.server.ready.killOnServerStop.description%", + "default": false } } } diff --git a/extensions/debug-server-ready/package.nls.json b/extensions/debug-server-ready/package.nls.json index c5212d7ee02..45169b0095b 100644 --- a/extensions/debug-server-ready/package.nls.json +++ b/extensions/debug-server-ready/package.nls.json @@ -10,5 +10,6 @@ "debug.server.ready.pattern.description": "Server is ready if this pattern appears on the debug console. The first capture group must include a URI or a port number.", "debug.server.ready.uriFormat.description": "A format string used when constructing the URI from a port number. The first '%s' is substituted with the port number.", "debug.server.ready.webRoot.description": "Value passed to the debug configuration for the 'Debugger for Chrome'.", + "debug.server.ready.killOnServerStop.description": "Stop the child session when the parent session stopped.", "debug.server.ready.debugConfigName.description": "Name of the launch configuration to run." } diff --git a/extensions/debug-server-ready/src/extension.ts b/extensions/debug-server-ready/src/extension.ts index 547125c7c03..592dec936eb 100644 --- a/extensions/debug-server-ready/src/extension.ts +++ b/extensions/debug-server-ready/src/extension.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import * as util from 'util'; +import { randomUUID } from 'crypto'; const PATTERN = 'listening on.* (https?://\\S+|[0-9]+)'; // matches "listening on port 3000" or "Now listening on: https://localhost:5001" const URI_PORT_FORMAT = 'http://localhost:%s'; @@ -17,6 +18,7 @@ interface ServerReadyAction { uriFormat?: string; webRoot?: string; name?: string; + killOnServerStop?: boolean; } class Trigger { @@ -40,6 +42,7 @@ class ServerReadyDetector extends vscode.Disposable { private shellPid?: number; private regexp: RegExp; private disposables: vscode.Disposable[] = []; + private lateDisposables = new Set([]); static start(session: vscode.DebugSession): ServerReadyDetector | undefined { if (session.configuration.serverReadyAction) { @@ -109,6 +112,11 @@ class ServerReadyDetector extends vscode.Disposable { this.disposables = []; } + override dispose() { + this.lateDisposables.forEach(d => d.dispose()); + return super.dispose(); + } + detectPattern(s: string): boolean { if (!this.trigger.hasFired) { const matches = this.regexp.exec(s); @@ -153,25 +161,25 @@ class ServerReadyDetector extends vscode.Disposable { this.openExternalWithUri(session, uri); } - private openExternalWithUri(session: vscode.DebugSession, uri: string) { + private async openExternalWithUri(session: vscode.DebugSession, uri: string) { const args: ServerReadyAction = session.configuration.serverReadyAction; switch (args.action || 'openExternally') { case 'openExternally': - vscode.env.openExternal(vscode.Uri.parse(uri)); + await vscode.env.openExternal(vscode.Uri.parse(uri)); break; case 'debugWithChrome': - this.debugWithBrowser('pwa-chrome', session, uri); + await this.debugWithBrowser('pwa-chrome', session, uri); break; case 'debugWithEdge': - this.debugWithBrowser('pwa-msedge', session, uri); + await this.debugWithBrowser('pwa-msedge', session, uri); break; case 'startDebugging': - vscode.debug.startDebugging(session.workspaceFolder, args.name || 'unspecified'); + await this.startNamedDebugSession(session, args.name || 'unspecified'); break; default: @@ -180,13 +188,104 @@ class ServerReadyDetector extends vscode.Disposable { } } - private debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) { + private async debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) { + const args = session.configuration.serverReadyAction as ServerReadyAction; + if (!args.killOnServerStop) { + await this.startBrowserDebugSession(type, session, uri); + return; + } + + const trackerId = randomUUID(); + const cts = new vscode.CancellationTokenSource(); + const newSessionPromise = this.catchStartedDebugSession(session => session.configuration._debugServerReadySessionId === trackerId, cts.token); + + if (!await this.startBrowserDebugSession(type, session, uri, trackerId)) { + cts.cancel(); + cts.dispose(); + return; + } + + const createdSession = await newSessionPromise; + cts.dispose(); + + if (!createdSession) { + return; + } + + const stopListener = vscode.debug.onDidTerminateDebugSession(async (terminated) => { + if (terminated === session) { + stopListener.dispose(); + this.lateDisposables.delete(stopListener); + await vscode.debug.stopDebugging(createdSession); + } + }); + this.lateDisposables.add(stopListener); + } + + private startBrowserDebugSession(type: string, session: vscode.DebugSession, uri: string, trackerId?: string) { return vscode.debug.startDebugging(session.workspaceFolder, { type, name: 'Browser Debug', request: 'launch', url: uri, - webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT + webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT, + _debugServerReadySessionId: trackerId, + }); + } + + private async startNamedDebugSession(session: vscode.DebugSession, name: string) { + const args = session.configuration.serverReadyAction as ServerReadyAction; + if (!args.killOnServerStop) { + await vscode.debug.startDebugging(session.workspaceFolder, name); + return; + } + + const cts = new vscode.CancellationTokenSource(); + const newSessionPromise = this.catchStartedDebugSession(x => x.name === name, cts.token); + + if (!await vscode.debug.startDebugging(session.workspaceFolder, name)) { + cts.cancel(); + cts.dispose(); + return; + } + + const createdSession = await newSessionPromise; + cts.dispose(); + + if (!createdSession) { + return; + } + + const stopListener = vscode.debug.onDidTerminateDebugSession(async (terminated) => { + if (terminated === session) { + stopListener.dispose(); + this.lateDisposables.delete(stopListener); + await vscode.debug.stopDebugging(createdSession); + } + }); + this.lateDisposables.add(stopListener); + } + + private catchStartedDebugSession(predicate: (session: vscode.DebugSession) => boolean, cancellationToken: vscode.CancellationToken): Promise { + return new Promise(_resolve => { + const done = (value?: vscode.DebugSession) => { + listener.dispose(); + cancellationListener.dispose(); + this.lateDisposables.delete(listener); + this.lateDisposables.delete(cancellationListener); + _resolve(value); + }; + + const cancellationListener = cancellationToken.onCancellationRequested(done); + const listener = vscode.debug.onDidStartDebugSession(session => { + if (predicate(session)) { + done(session); + } + }); + + // In case the debug session of interest was never caught anyhow. + this.lateDisposables.add(listener); + this.lateDisposables.add(cancellationListener); }); } } From 93c3f3202bc42accbc155d264ae2c6fae86a51cb Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 15:41:23 +0100 Subject: [PATCH 38/67] don't use `__$__nodeRequire` to fetch product configuration --- src/bootstrap-fork.js | 4 +++- src/bootstrap-window.js | 4 ++++ src/main.js | 4 ++++ src/typings/vscode-globals.d.ts | 9 +++++++++ src/vs/platform/product/common/product.ts | 16 +++++----------- test/unit/electron/renderer.js | 3 +++ 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 9d760ddf101..6d11c4a57e8 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -47,11 +47,13 @@ globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { } }); +// VSCODE_GLOBALS: package/product.json +globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); +globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); // Load AMD entry point require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); - //#region Helpers function pipeLoggingToParent() { diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index f8b895c2d77..40a2ff21a49 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -122,6 +122,10 @@ } }); + // VSCODE_GLOBALS: package/product.json + globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)(configuration.appRoot + '/product.json'); + globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)(configuration.appRoot + '/package.json'); + const loaderConfig = { baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, 'vs/nls': nlsConfig, diff --git a/src/main.js b/src/main.js index 4c43e0923b7..c40595aa75d 100644 --- a/src/main.js +++ b/src/main.js @@ -151,6 +151,10 @@ function startup(codeCachePath, nlsConfig) { } }); + // VSCODE_GLOBALS: package/product.json + globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); + globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); + // Load main in AMD perf.mark('code/willLoadMainBundle'); require('./bootstrap-amd').load('vs/code/electron-main/main', () => { diff --git a/src/typings/vscode-globals.d.ts b/src/typings/vscode-globals.d.ts index f8d32c405bf..35e81033fc1 100644 --- a/src/typings/vscode-globals.d.ts +++ b/src/typings/vscode-globals.d.ts @@ -5,6 +5,15 @@ declare global { + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PRODUCT_JSON: Record; + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PACKAGE_JSON: Record; + /** * @deprecated node modules that are in used in a context that * shouldn't have access to node_modules (node-free renderer or diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 3f50bef5ca2..ef798fa4699 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FileAccess } from 'vs/base/common/network'; import { globals } from 'vs/base/common/platform'; import { env } from 'vs/base/common/process'; import { IProductConfiguration } from 'vs/base/common/product'; -import { dirname, joinPath } from 'vs/base/common/resources'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; /** @@ -24,14 +22,10 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.context !== ' throw new Error('Sandbox: unable to resolve product configuration from preload script.'); } } - -// Native node.js environment -else if (typeof require?.__$__nodeRequire === 'function') { - - // Obtain values from product.json and package.json - const rootPath = dirname(FileAccess.asFileUri('')); - - product = require.__$__nodeRequire(joinPath(rootPath, 'product.json').fsPath); +// _VSCODE environment +else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { + // Obtain values from product.json and package.json-data + product = globalThis._VSCODE_PRODUCT_JSON as IProductConfiguration; // Running out of sources if (env['VSCODE_DEV']) { @@ -47,7 +41,7 @@ else if (typeof require?.__$__nodeRequire === 'function') { // want to have it running out of sources so we // read it from package.json only when we need it. if (!product.version) { - const pkg = require.__$__nodeRequire(joinPath(rootPath, 'package.json').fsPath) as { version: string }; + const pkg = globalThis._VSCODE_PACKAGE_JSON as { version: string }; Object.assign(product, { version: pkg.version diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 3a7cc2fb66e..77a10b3421d 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -81,6 +81,9 @@ globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { return target[mod]; } }); +// VSCODE_GLOBALS: package/product.json +globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)('../../../product.json'); +globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)('../../../package.json'); const _tests_glob = '**/test/**/*.test.js'; let loader; From 10ae860842ede0b4ded3c86b09e03c54aec7b653 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 17 Nov 2022 09:42:55 -0500 Subject: [PATCH 39/67] Remove i18n causing classifier to fail due to missing label (#166587) --- .github/classifier.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/classifier.json b/.github/classifier.json index f39be53b8b9..1f162f2e03f 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -84,7 +84,6 @@ "grammar": {"assign": ["mjbvz"]}, "grid-view": {"assign": ["joaomoreno"]}, "html": {"assign": ["aeschli"]}, - "i18n": {"assign": []}, "icon-brand": {"assign": []}, "icons-product": {"assign": ["daviddossett"]}, "inlay-hints": {"assign": ["jrieken", "hediet"]}, From 3c372cf16056541ee70e06a6ac6d2b245f6f2b04 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 15:47:18 +0100 Subject: [PATCH 40/67] deprecate and scrary message for `__$__nodeRequire` --- src/typings/require.d.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts index 671692c88da..3fda6d6981d 100644 --- a/src/typings/require.d.ts +++ b/src/typings/require.d.ts @@ -45,10 +45,17 @@ interface NodeRequire { * @deprecated use `FileAccess.asFileUri()` for node.js contexts or `FileAccess.asBrowserUri` for browser contexts. */ toUrl(path: string): string; + + /** + * @deprecated MUST not be used anymore + * + * With the move from AMD to ESM we cannot use this anymore. There will be NO MORE node require like this. + */ + __$__nodeRequire(moduleName: string): T; + (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; config(data: any): any; onError: Function; - __$__nodeRequire(moduleName: string): T; getStats?(): ReadonlyArray; hasDependencyCycle?(): boolean; define(amdModuleId: string, dependencies: string[], callback: (...args: any[]) => any): any; From a0f314404ec65ba7bf8ee6f39399e030914725a3 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 16:10:52 +0100 Subject: [PATCH 41/67] have two files to declare globals, allow monaco-edt to see one of them --- src/tsconfig.monaco.json | 1 + ...obals.d.ts => vscode-globals-modules.d.ts} | 11 ++-------- src/typings/vscode-globals-product.d.ts | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 9 deletions(-) rename src/typings/{vscode-globals.d.ts => vscode-globals-modules.d.ts} (78%) create mode 100644 src/typings/vscode-globals-product.d.ts diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 057aa8748ae..256f406fb6c 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -18,6 +18,7 @@ "include": [ "typings/require.d.ts", "typings/thenable.d.ts", + "typings/vscode-globals-product.d.ts", "vs/loader.d.ts", "vs/monaco.d.ts", "vs/editor/*", diff --git a/src/typings/vscode-globals.d.ts b/src/typings/vscode-globals-modules.d.ts similarity index 78% rename from src/typings/vscode-globals.d.ts rename to src/typings/vscode-globals-modules.d.ts index 35e81033fc1..443c2b687db 100644 --- a/src/typings/vscode-globals.d.ts +++ b/src/typings/vscode-globals-modules.d.ts @@ -3,16 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare global { +// AMD2ESM mirgation relevant - /** - * @deprecated You MUST use `IProductService` whenever possible. - */ - var _VSCODE_PRODUCT_JSON: Record; - /** - * @deprecated You MUST use `IProductService` whenever possible. - */ - var _VSCODE_PACKAGE_JSON: Record; +declare global { /** * @deprecated node modules that are in used in a context that diff --git a/src/typings/vscode-globals-product.d.ts b/src/typings/vscode-globals-product.d.ts new file mode 100644 index 00000000000..780a6477de8 --- /dev/null +++ b/src/typings/vscode-globals-product.d.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. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM mirgation relevant + +declare global { + + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PRODUCT_JSON: Record; + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PACKAGE_JSON: Record; + +} + +// fake export to make global work +export { } From 4759e0b9288cfaf605252ef0996f49b1f502c764 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 17 Nov 2022 16:20:33 +0100 Subject: [PATCH 42/67] refactor styling of toggle, dialog, inputbox & checkbox (#166542) * refactor styling of toggle, dialog, inputbox & checkbox * simplify abstract tree styling Co-authored-by: Joao Moreno --- .../browser/ui/actionbar/actionViewItems.ts | 4 +- src/vs/base/browser/ui/dialog/dialog.ts | 117 +++++++------- src/vs/base/browser/ui/findinput/findInput.ts | 126 +-------------- .../browser/ui/findinput/findInputToggles.ts | 7 +- .../base/browser/ui/findinput/replaceInput.ts | 111 +------------ src/vs/base/browser/ui/inputbox/inputBox.ts | 129 ++++++--------- src/vs/base/browser/ui/list/listWidget.ts | 7 +- src/vs/base/browser/ui/toggle/toggle.ts | 74 ++++----- src/vs/base/browser/ui/tree/abstractTree.ts | 94 ++++++----- src/vs/base/browser/ui/tree/asyncDataTree.ts | 3 +- .../parts/quickinput/browser/quickInput.ts | 6 +- .../parts/quickinput/browser/quickInputBox.ts | 12 +- .../test/browser/quickinput.test.ts | 5 +- .../test/browser/ui/tree/dataTree.test.ts | 4 +- .../processExplorer/processExplorerMain.ts | 2 +- .../contrib/find/browser/findController.ts | 2 +- .../contrib/find/browser/findOptionsWidget.ts | 40 ++--- .../editor/contrib/find/browser/findWidget.ts | 46 ++---- src/vs/platform/list/browser/listService.ts | 4 +- .../platform/quickinput/browser/quickInput.ts | 30 +--- .../platform/theme/browser/defaultStyles.ts | 134 +++++++++++----- src/vs/platform/theme/common/colorRegistry.ts | 4 +- src/vs/platform/theme/common/styler.ts | 148 ++---------------- .../workbench/browser/parts/compositePart.ts | 4 +- .../parts/dialogs/dialog.web.contribution.ts | 4 +- .../browser/parts/dialogs/dialogHandler.ts | 14 +- .../browser/parts/editor/editorGroupView.ts | 4 +- .../notifications/notificationsViewer.ts | 4 +- .../workbench/browser/parts/views/checkbox.ts | 9 +- .../workbench/browser/parts/views/treeView.ts | 2 +- .../browser/parts/views/viewFilter.ts | 7 +- .../workbench/browser/parts/views/viewPane.ts | 4 +- .../browser/find/simpleFindWidget.ts | 30 +--- .../contrib/debug/browser/baseDebugView.ts | 10 +- .../contrib/debug/browser/breakpointsView.ts | 17 +- .../contrib/debug/browser/replViewer.ts | 3 +- .../contrib/debug/browser/variablesView.ts | 3 +- .../debug/browser/watchExpressionsView.ts | 3 +- .../extensions/browser/extensionEditor.ts | 4 +- .../files/browser/views/explorerViewer.ts | 9 +- .../view/editors/inputCodeEditorView.ts | 12 +- .../contrib/find/notebookFindReplaceWidget.ts | 72 +++------ .../contrib/find/notebookFindWidget.ts | 6 - .../browser/view/cellParts/cellProgressBar.ts | 6 +- .../contrib/outline/browser/outlinePane.ts | 4 +- .../preferences/browser/keybindingWidgets.ts | 11 +- .../preferences/browser/keybindingsEditor.ts | 27 +--- .../preferences/browser/preferencesWidgets.ts | 4 +- .../preferences/browser/settingsTree.ts | 35 +++-- .../preferences/browser/settingsWidgets.ts | 43 ++--- .../contrib/remote/browser/tunnelView.ts | 14 +- .../search/browser/patternInputWidget.ts | 32 ++-- .../contrib/search/browser/searchView.ts | 11 +- .../contrib/search/browser/searchWidget.ts | 27 ++-- .../searchEditor/browser/searchEditor.ts | 11 +- .../terminal/browser/terminalFindWidget.ts | 6 +- .../browser/terminalRunRecentQuickPick.ts | 11 +- .../terminal/browser/terminalTabsList.ts | 9 +- .../contrib/watermark/browser/watermark.ts | 4 +- .../contrib/webview/browser/webviewElement.ts | 6 - .../browser/gettingStarted.ts | 3 +- .../workspace/browser/workspaceTrustEditor.ts | 12 +- .../parts/dialogs/dialog.contribution.ts | 4 +- .../progress/browser/progressService.ts | 11 +- 64 files changed, 560 insertions(+), 1061 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index a4a8f5204a3..7722ef6b3d8 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -12,6 +12,7 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { ISelectBoxOptions, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { Action, ActionRunner, IAction, IActionChangeEvent, IActionRunner, Separator } from 'vs/base/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; @@ -261,6 +262,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; label?: boolean; keybinding?: string | null; + toggleStyles?: IToggleStyles; } export class ActionViewItem extends BaseActionViewItem { @@ -270,7 +272,7 @@ export class ActionViewItem extends BaseActionViewItem { private cssClass?: string; - constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { + constructor(context: unknown, action: IAction, options: IActionViewItemOptions) { super(context, action, options); this.options = options; diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 3ec1b02ca80..e4411793cd7 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -8,10 +8,9 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ButtonBar, ButtonWithDescription, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { ICheckboxStyles, Checkbox } from 'vs/base/browser/ui/toggle/toggle'; -import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IInputBoxStyles, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Action } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -39,6 +38,9 @@ export interface IDialogOptions { readonly disableCloseAction?: boolean; readonly disableDefaultAction?: boolean; readonly buttonStyles: IButtonStyles; + readonly checkboxStyles: ICheckboxStyles; + readonly inputBoxStyles: IInputBoxStyles; + readonly dialogStyles: IDialogStyles; } export interface IDialogResult { @@ -47,19 +49,15 @@ export interface IDialogResult { readonly values?: string[]; } -export interface IDialogStyles extends ICheckboxStyles { - readonly dialogForeground?: Color; - readonly dialogBackground?: Color; - readonly dialogShadow?: Color; - readonly dialogBorder?: Color; - readonly errorIconForeground?: Color; - readonly warningIconForeground?: Color; - readonly infoIconForeground?: Color; - readonly inputBackground?: Color; - readonly inputForeground?: Color; - readonly inputBorder?: Color; - readonly textLinkForeground?: Color; - +export interface IDialogStyles { + readonly dialogForeground: string | undefined; + readonly dialogBackground: string | undefined; + readonly dialogShadow: string | undefined; + readonly dialogBorder: string | undefined; + readonly errorIconForeground: string | undefined; + readonly warningIconForeground: string | undefined; + readonly infoIconForeground: string | undefined; + readonly textLinkForeground: string | undefined; } interface ButtonMapEntry { @@ -78,7 +76,6 @@ export class Dialog extends Disposable { private readonly checkbox: Checkbox | undefined; private readonly toolbarContainer: HTMLElement; private buttonBar: ButtonBar | undefined; - private styles: IDialogStyles | undefined; private focusToReturn: HTMLElement | undefined; private readonly inputs: InputBox[]; private readonly buttons: string[]; @@ -140,6 +137,7 @@ export class Dialog extends Disposable { const inputBox = this._register(new InputBox(inputRowElement, undefined, { placeholder: input.placeholder, type: input.type ?? 'text', + inputBoxStyles: options.inputBoxStyles })); if (input.value) { @@ -155,7 +153,9 @@ export class Dialog extends Disposable { if (this.options.checkboxLabel) { const checkboxRowElement = this.messageContainer.appendChild($('.dialog-checkbox-row')); - const checkbox = this.checkbox = this._register(new Checkbox(this.options.checkboxLabel, !!this.options.checkboxChecked)); + const checkbox = this.checkbox = this._register( + new Checkbox(this.options.checkboxLabel, !!this.options.checkboxChecked, options.checkboxStyles) + ); checkboxRowElement.appendChild(checkbox.domNode); @@ -166,6 +166,8 @@ export class Dialog extends Disposable { const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row')); this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar')); + + this.applyStyles(); } private getIconAriaLabel(): string { @@ -391,7 +393,7 @@ export class Dialog extends Disposable { }); })); - actionBar.push(action, { icon: true, label: false, }); + actionBar.push(action, { icon: true, label: false }); } this.applyStyles(); @@ -416,60 +418,47 @@ export class Dialog extends Disposable { } private applyStyles() { - if (this.styles) { - const style = this.styles; + const style = this.options.dialogStyles; - const fgColor = style.dialogForeground; - const bgColor = style.dialogBackground; - const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; - const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; - const linkFgColor = style.textLinkForeground; + const fgColor = style.dialogForeground; + const bgColor = style.dialogBackground; + const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; + const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; + const linkFgColor = style.textLinkForeground; - this.shadowElement.style.boxShadow = shadowColor; + this.shadowElement.style.boxShadow = shadowColor; - this.element.style.color = fgColor?.toString() ?? ''; - this.element.style.backgroundColor = bgColor?.toString() ?? ''; - this.element.style.border = border; + this.element.style.color = fgColor?.toString() ?? ''; + this.element.style.backgroundColor = bgColor?.toString() ?? ''; + this.element.style.border = border; - this.checkbox?.style(style); + // TODO fix + // if (fgColor && bgColor) { + // const messageDetailColor = fgColor.transparent(.9); + // this.messageDetailElement.style.mixBlendMode = messageDetailColor.makeOpaque(bgColor).toString(); + // } - if (fgColor && bgColor) { - const messageDetailColor = fgColor.transparent(.9); - this.messageDetailElement.style.color = messageDetailColor.makeOpaque(bgColor).toString(); - } - - if (linkFgColor) { - for (const el of this.messageContainer.getElementsByTagName('a')) { - el.style.color = linkFgColor.toString(); - } - } - - let color; - switch (this.options.type) { - case 'error': - color = style.errorIconForeground; - break; - case 'warning': - color = style.warningIconForeground; - break; - default: - color = style.infoIconForeground; - break; - } - if (color) { - this.iconElement.style.color = color.toString(); - } - - for (const input of this.inputs) { - input.style(style); + if (linkFgColor) { + for (const el of this.messageContainer.getElementsByTagName('a')) { + el.style.color = linkFgColor; } } - } - style(style: IDialogStyles): void { - this.styles = style; - - this.applyStyles(); + let color; + switch (this.options.type) { + case 'error': + color = style.errorIconForeground; + break; + case 'warning': + color = style.warningIconForeground; + break; + default: + color = style.infoIconForeground; + break; + } + if (color) { + this.iconElement.style.color = color; + } } override dispose(): void { diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index d9809ddf156..39fd66a7251 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -11,7 +11,6 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from 'vs/base/browser/ui/findinput/findInputToggles'; import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; @@ -19,7 +18,7 @@ import * as nls from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -export interface IFindInputOptions extends IFindInputStyles { +export interface IFindInputOptions { readonly placeholder?: string; readonly width?: number; readonly validation?: IInputValidator; @@ -35,12 +34,8 @@ export interface IFindInputOptions extends IFindInputStyles { readonly history?: string[]; readonly additionalToggles?: Toggle[]; readonly showHistoryHint?: () => boolean; -} - -export interface IFindInputStyles extends IInputBoxStyles { - inputActiveOptionBorder?: Color; - inputActiveOptionForeground?: Color; - inputActiveOptionBackground?: Color; + readonly toggleStyles: IToggleStyles; + readonly inputBoxStyles: IInputBoxStyles; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -57,23 +52,6 @@ export class FindInput extends Widget { private imeSessionInProgress = false; private additionalTogglesDisposables: DisposableStore = new DisposableStore(); - protected inputActiveOptionBorder?: Color; - protected inputActiveOptionForeground?: Color; - protected inputActiveOptionBackground?: Color; - protected inputBackground?: Color; - protected inputForeground?: Color; - protected inputBorder?: Color; - - protected inputValidationInfoBorder?: Color; - protected inputValidationInfoBackground?: Color; - protected inputValidationInfoForeground?: Color; - protected inputValidationWarningBorder?: Color; - protected inputValidationWarningBackground?: Color; - protected inputValidationWarningForeground?: Color; - protected inputValidationErrorBorder?: Color; - protected inputValidationErrorBackground?: Color; - protected inputValidationErrorForeground?: Color; - protected readonly controls: HTMLDivElement; protected readonly regex?: RegexToggle; protected readonly wholeWords?: WholeWordsToggle; @@ -110,23 +88,6 @@ export class FindInput extends Widget { this.label = options.label || NLS_DEFAULT_LABEL; this.showCommonFindToggles = !!options.showCommonFindToggles; - this.inputActiveOptionBorder = options.inputActiveOptionBorder; - this.inputActiveOptionForeground = options.inputActiveOptionForeground; - this.inputActiveOptionBackground = options.inputActiveOptionBackground; - this.inputBackground = options.inputBackground; - this.inputForeground = options.inputForeground; - this.inputBorder = options.inputBorder; - - this.inputValidationInfoBorder = options.inputValidationInfoBorder; - this.inputValidationInfoBackground = options.inputValidationInfoBackground; - this.inputValidationInfoForeground = options.inputValidationInfoForeground; - this.inputValidationWarningBorder = options.inputValidationWarningBorder; - this.inputValidationWarningBackground = options.inputValidationWarningBackground; - this.inputValidationWarningForeground = options.inputValidationWarningForeground; - this.inputValidationErrorBorder = options.inputValidationErrorBorder; - this.inputValidationErrorBackground = options.inputValidationErrorBackground; - this.inputValidationErrorForeground = options.inputValidationErrorForeground; - const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || ''; const appendWholeWordsLabel = options.appendWholeWordsLabel || ''; const appendRegexLabel = options.appendRegexLabel || ''; @@ -144,32 +105,19 @@ export class FindInput extends Widget { validationOptions: { validation: this.validation }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, history, showHistoryHint: options.showHistoryHint, flexibleHeight, flexibleWidth, - flexibleMaxHeight + flexibleMaxHeight, + inputBoxStyles: options.inputBoxStyles, })); if (this.showCommonFindToggles) { this.regex = this._register(new RegexToggle({ appendTitle: appendRegexLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground + ...options.toggleStyles })); this._register(this.regex.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -185,9 +133,7 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: appendWholeWordsLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground + ...options.toggleStyles })); this._register(this.wholeWords.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -200,9 +146,7 @@ export class FindInput extends Widget { this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: appendCaseSensitiveLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground + ...options.toggleStyles })); this._register(this.caseSensitive.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -375,60 +319,6 @@ export class FindInput extends Widget { this.inputBox.addToHistory(); } - public style(styles: IFindInputStyles): void { - this.inputActiveOptionBorder = styles.inputActiveOptionBorder; - this.inputActiveOptionForeground = styles.inputActiveOptionForeground; - this.inputActiveOptionBackground = styles.inputActiveOptionBackground; - this.inputBackground = styles.inputBackground; - this.inputForeground = styles.inputForeground; - this.inputBorder = styles.inputBorder; - - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoForeground = styles.inputValidationInfoForeground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningForeground = styles.inputValidationWarningForeground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; - - this.applyStyles(); - } - - protected applyStyles(): void { - if (this.domNode) { - const toggleStyles: IToggleStyles = { - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground, - }; - this.regex?.style(toggleStyles); - this.wholeWords?.style(toggleStyles); - this.caseSensitive?.style(toggleStyles); - - for (const toggle of this.additionalToggles) { - toggle.style(toggleStyles); - } - - const inputBoxStyles: IInputBoxStyles = { - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder - }; - this.inputBox.style(inputBoxStyles); - } - } - public select(): void { this.inputBox.select(); } diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index 8f6f30dda44..591ab981577 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -5,15 +5,14 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import * as nls from 'vs/nls'; export interface IFindInputToggleOpts { readonly appendTitle: string; readonly isChecked: boolean; - readonly inputActiveOptionBorder?: Color; - readonly inputActiveOptionForeground?: Color; - readonly inputActiveOptionBackground?: Color; + readonly inputActiveOptionBorder: string | undefined; + readonly inputActiveOptionForeground: string | undefined; + readonly inputActiveOptionBackground: string | undefined; } const NLS_CASE_SENSITIVE_TOGGLE_LABEL = nls.localize('caseDescription', "Match Case"); diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index cbfd7136436..a9b668c7c05 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -6,20 +6,19 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Toggle, IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IFindInputToggleOpts } from 'vs/base/browser/ui/findinput/findInputToggles'; import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; -export interface IReplaceInputOptions extends IReplaceInputStyles { +export interface IReplaceInputOptions { readonly placeholder?: string; readonly width?: number; readonly validation?: IInputValidator; @@ -31,12 +30,8 @@ export interface IReplaceInputOptions extends IReplaceInputStyles { readonly appendPreserveCaseLabel?: string; readonly history?: string[]; readonly showHistoryHint?: () => boolean; -} - -export interface IReplaceInputStyles extends IInputBoxStyles { - inputActiveOptionBorder?: Color; - inputActiveOptionForeground?: Color; - inputActiveOptionBackground?: Color; + readonly inputBoxStyles: IInputBoxStyles; + readonly toggleStyles: IToggleStyles; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -66,23 +61,6 @@ export class ReplaceInput extends Widget { private label: string; private fixFocusOnOptionClickEnabled = true; - private inputActiveOptionBorder?: Color; - private inputActiveOptionForeground?: Color; - private inputActiveOptionBackground?: Color; - private inputBackground?: Color; - private inputForeground?: Color; - private inputBorder?: Color; - - private inputValidationInfoBorder?: Color; - private inputValidationInfoBackground?: Color; - private inputValidationInfoForeground?: Color; - private inputValidationWarningBorder?: Color; - private inputValidationWarningBackground?: Color; - private inputValidationWarningForeground?: Color; - private inputValidationErrorBorder?: Color; - private inputValidationErrorBackground?: Color; - private inputValidationErrorForeground?: Color; - private preserveCase: PreserveCaseToggle; private cachedOptionsWidth: number = 0; public domNode: HTMLElement; @@ -113,23 +91,6 @@ export class ReplaceInput extends Widget { this.validation = options.validation; this.label = options.label || NLS_DEFAULT_LABEL; - this.inputActiveOptionBorder = options.inputActiveOptionBorder; - this.inputActiveOptionForeground = options.inputActiveOptionForeground; - this.inputActiveOptionBackground = options.inputActiveOptionBackground; - this.inputBackground = options.inputBackground; - this.inputForeground = options.inputForeground; - this.inputBorder = options.inputBorder; - - this.inputValidationInfoBorder = options.inputValidationInfoBorder; - this.inputValidationInfoBackground = options.inputValidationInfoBackground; - this.inputValidationInfoForeground = options.inputValidationInfoForeground; - this.inputValidationWarningBorder = options.inputValidationWarningBorder; - this.inputValidationWarningBackground = options.inputValidationWarningBackground; - this.inputValidationWarningForeground = options.inputValidationWarningForeground; - this.inputValidationErrorBorder = options.inputValidationErrorBorder; - this.inputValidationErrorBackground = options.inputValidationErrorBackground; - this.inputValidationErrorForeground = options.inputValidationErrorForeground; - const appendPreserveCaseLabel = options.appendPreserveCaseLabel || ''; const history = options.history || []; const flexibleHeight = !!options.flexibleHeight; @@ -145,31 +106,18 @@ export class ReplaceInput extends Widget { validationOptions: { validation: this.validation }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, history, showHistoryHint: options.showHistoryHint, flexibleHeight, flexibleWidth, - flexibleMaxHeight + flexibleMaxHeight, + inputBoxStyles: options.inputBoxStyles })); this.preserveCase = this._register(new PreserveCaseToggle({ appendTitle: appendPreserveCaseLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground, + ...options.toggleStyles })); this._register(this.preserveCase.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -277,52 +225,7 @@ export class ReplaceInput extends Widget { this.inputBox.addToHistory(); } - public style(styles: IReplaceInputStyles): void { - this.inputActiveOptionBorder = styles.inputActiveOptionBorder; - this.inputActiveOptionForeground = styles.inputActiveOptionForeground; - this.inputActiveOptionBackground = styles.inputActiveOptionBackground; - this.inputBackground = styles.inputBackground; - this.inputForeground = styles.inputForeground; - this.inputBorder = styles.inputBorder; - - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoForeground = styles.inputValidationInfoForeground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningForeground = styles.inputValidationWarningForeground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; - - this.applyStyles(); - } - protected applyStyles(): void { - if (this.domNode) { - const toggleStyles: IToggleStyles = { - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground, - }; - this.preserveCase.style(toggleStyles); - - const inputBoxStyles: IInputBoxStyles = { - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder - }; - this.inputBox.style(inputBoxStyles); - } } public select(): void { diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index c3d50ef272c..53f918e163d 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -14,10 +14,8 @@ import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contex import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { HistoryNavigator } from 'vs/base/common/history'; -import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./inputBox'; import * as nls from 'vs/nls'; @@ -25,7 +23,7 @@ import * as nls from 'vs/nls'; const $ = dom.$; -export interface IInputOptions extends IInputBoxStyles { +export interface IInputOptions { readonly placeholder?: string; readonly showPlaceholderOnFocus?: boolean; readonly tooltip?: string; @@ -36,21 +34,22 @@ export interface IInputOptions extends IInputBoxStyles { readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray; + readonly inputBoxStyles: IInputBoxStyles; } export interface IInputBoxStyles { - readonly inputBackground?: Color; - readonly inputForeground?: Color; - readonly inputBorder?: Color; - readonly inputValidationInfoBorder?: Color; - readonly inputValidationInfoBackground?: Color; - readonly inputValidationInfoForeground?: Color; - readonly inputValidationWarningBorder?: Color; - readonly inputValidationWarningBackground?: Color; - readonly inputValidationWarningForeground?: Color; - readonly inputValidationErrorBorder?: Color; - readonly inputValidationErrorBackground?: Color; - readonly inputValidationErrorForeground?: Color; + readonly inputBackground: string | undefined; + readonly inputForeground: string | undefined; + readonly inputBorder: string | undefined; + readonly inputValidationInfoBorder: string | undefined; + readonly inputValidationInfoBackground: string | undefined; + readonly inputValidationInfoForeground: string | undefined; + readonly inputValidationWarningBorder: string | undefined; + readonly inputValidationWarningBackground: string | undefined; + readonly inputValidationWarningForeground: string | undefined; + readonly inputValidationErrorBorder: string | undefined; + readonly inputValidationErrorBackground: string | undefined; + readonly inputValidationErrorForeground: string | undefined; } export interface IInputValidator { @@ -78,15 +77,19 @@ export interface IRange { end: number; } -const defaultOpts = { - inputBackground: Color.fromHex('#3C3C3C'), - inputForeground: Color.fromHex('#CCCCCC'), - inputValidationInfoBorder: Color.fromHex('#55AAFF'), - inputValidationInfoBackground: Color.fromHex('#063B49'), - inputValidationWarningBorder: Color.fromHex('#B89500'), - inputValidationWarningBackground: Color.fromHex('#352A05'), - inputValidationErrorBorder: Color.fromHex('#BE1100'), - inputValidationErrorBackground: Color.fromHex('#5A1D1D') +export const unthemedInboxStyles: IInputBoxStyles = { + inputBackground: '#3C3C3C', + inputForeground: '#CCCCCC', + inputValidationInfoBorder: '#55AAFF', + inputValidationInfoBackground: '#063B49', + inputValidationWarningBorder: '#B89500', + inputValidationWarningBackground: '#352A05', + inputValidationErrorBorder: '#BE1100', + inputValidationErrorBackground: '#5A1D1D', + inputBorder: undefined, + inputValidationErrorForeground: undefined, + inputValidationInfoForeground: undefined, + inputValidationWarningForeground: undefined }; export class InputBox extends Widget { @@ -94,7 +97,7 @@ export class InputBox extends Widget { element: HTMLElement; protected input: HTMLInputElement; private actionbar?: ActionBar; - private options: IInputOptions; + private readonly options: IInputOptions; private message: IMessage | null; protected placeholder: string; private tooltip: string; @@ -108,51 +111,23 @@ export class InputBox extends Widget { private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; - private inputBackground?: Color; - private inputForeground?: Color; - private inputBorder?: Color; - - private inputValidationInfoBorder?: Color; - private inputValidationInfoBackground?: Color; - private inputValidationInfoForeground?: Color; - private inputValidationWarningBorder?: Color; - private inputValidationWarningBackground?: Color; - private inputValidationWarningForeground?: Color; - private inputValidationErrorBorder?: Color; - private inputValidationErrorBackground?: Color; - private inputValidationErrorForeground?: Color; - private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; private _onDidHeightChange = this._register(new Emitter()); public readonly onDidHeightChange: Event = this._onDidHeightChange.event; - constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options?: IInputOptions) { + constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IInputOptions) { super(); this.contextViewProvider = contextViewProvider; - this.options = options || Object.create(null); - mixin(this.options, defaultOpts, false); + this.options = options; + this.message = null; this.placeholder = this.options.placeholder || ''; this.tooltip = this.options.tooltip ?? (this.placeholder || ''); this.ariaLabel = this.options.ariaLabel || ''; - this.inputBackground = this.options.inputBackground; - this.inputForeground = this.options.inputForeground; - this.inputBorder = this.options.inputBorder; - - this.inputValidationInfoBorder = this.options.inputValidationInfoBorder; - this.inputValidationInfoBackground = this.options.inputValidationInfoBackground; - this.inputValidationInfoForeground = this.options.inputValidationInfoForeground; - this.inputValidationWarningBorder = this.options.inputValidationWarningBorder; - this.inputValidationWarningBackground = this.options.inputValidationWarningBackground; - this.inputValidationWarningForeground = this.options.inputValidationWarningForeground; - this.inputValidationErrorBorder = this.options.inputValidationErrorBorder; - this.inputValidationErrorBackground = this.options.inputValidationErrorBackground; - this.inputValidationErrorForeground = this.options.inputValidationErrorForeground; - if (this.options.validationOptions) { this.validation = this.options.validationOptions.validation; } @@ -436,11 +411,12 @@ export class InputBox extends Widget { return errorMsg?.type; } - public stylesForType(type: MessageType | undefined): { border: Color | undefined; background: Color | undefined; foreground: Color | undefined } { + public stylesForType(type: MessageType | undefined): { border: string | undefined; background: string | undefined; foreground: string | undefined } { + const styles = this.options.inputBoxStyles; switch (type) { - case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground }; - case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground }; - default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground, foreground: this.inputValidationErrorForeground }; + case MessageType.INFO: return { border: styles.inputValidationInfoBorder, background: styles.inputValidationInfoBackground, foreground: styles.inputValidationInfoForeground }; + case MessageType.WARNING: return { border: styles.inputValidationWarningBorder, background: styles.inputValidationWarningBackground, foreground: styles.inputValidationWarningForeground }; + default: return { border: styles.inputValidationErrorBorder, background: styles.inputValidationErrorBackground, foreground: styles.inputValidationErrorForeground }; } } @@ -555,37 +531,22 @@ export class InputBox extends Widget { this.layout(); } - public style(styles: IInputBoxStyles): void { - this.inputBackground = styles.inputBackground; - this.inputForeground = styles.inputForeground; - this.inputBorder = styles.inputBorder; - - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoForeground = styles.inputValidationInfoForeground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningForeground = styles.inputValidationWarningForeground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; - - this.applyStyles(); - } - protected applyStyles(): void { - const background = this.inputBackground ? this.inputBackground.toString() : ''; - const foreground = this.inputForeground ? this.inputForeground.toString() : ''; - const border = this.inputBorder ? this.inputBorder.toString() : ''; + const styles = this.options.inputBoxStyles; + + const background = styles.inputBackground ?? ''; + const foreground = styles.inputForeground ?? ''; + const border = styles.inputBorder ?? ''; this.element.style.backgroundColor = background; this.element.style.color = foreground; this.input.style.backgroundColor = 'inherit'; this.input.style.color = foreground; - this.element.style.borderWidth = border ? '1px' : ''; - this.element.style.borderStyle = border ? 'solid' : ''; - this.element.style.borderColor = border; + if (border) { + this.element.style.border = '1px solid ' + border; + } + } public layout(): void { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index c5efc9adb7a..cbdc849f114 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -9,7 +9,6 @@ import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions'; import { binarySearch, firstOrDefault, range } from 'vs/base/common/arrays'; @@ -963,7 +962,7 @@ export interface IListOptions extends IListOptionsUpdate { readonly initialSize?: Dimension; } -export interface IListStyles extends IFindInputStyles { +export interface IListStyles { listBackground?: Color; listFocusBackground?: Color; listFocusForeground?: Color; @@ -985,10 +984,6 @@ export interface IListStyles extends IFindInputStyles { listInactiveFocusOutline?: Color; listSelectionOutline?: Color; listHoverOutline?: Color; - listFilterWidgetBackground?: Color; - listFilterWidgetOutline?: Color; - listFilterWidgetNoMatchesOutline?: Color; - listFilterWidgetShadow?: Color; treeIndentGuidesStroke?: Color; tableColumnsBorder?: Color; tableOddRowsBackgroundColor?: Color; diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index b422d5a6d75..fb2083b9de1 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -8,7 +8,6 @@ import { BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/a import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; import { Codicon, CSSIcon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./toggle'; @@ -22,34 +21,38 @@ export interface IToggleOpts extends IToggleStyles { } export interface IToggleStyles { - inputActiveOptionBorder?: Color; - inputActiveOptionForeground?: Color; - inputActiveOptionBackground?: Color; + readonly inputActiveOptionBorder: string | undefined; + readonly inputActiveOptionForeground: string | undefined; + readonly inputActiveOptionBackground: string | undefined; } export interface ICheckboxStyles { - checkboxBackground?: Color; - checkboxBorder?: Color; - checkboxForeground?: Color; + readonly checkboxBackground: string | undefined; + readonly checkboxBorder: string | undefined; + readonly checkboxForeground: string | undefined; } -const defaultOpts = { - inputActiveOptionBorder: Color.fromHex('#007ACC00'), - inputActiveOptionForeground: Color.fromHex('#FFFFFF'), - inputActiveOptionBackground: Color.fromHex('#0E639C50') +export const unthemedToggleStyles = { + inputActiveOptionBorder: '#007ACC00', + inputActiveOptionForeground: '#FFFFFF', + inputActiveOptionBackground: '#0E639C50' }; export class ToggleActionViewItem extends BaseActionViewItem { protected readonly toggle: Toggle; - constructor(context: any, action: IAction, options: IActionViewItemOptions | undefined) { + constructor(context: any, action: IAction, options: IActionViewItemOptions) { super(context, action, options); + this.toggle = this._register(new Toggle({ actionClassName: this._action.class, isChecked: !!this._action.checked, title: (this.options).keybinding ? `${this._action.label} (${(this.options).keybinding})` : this._action.label, - notFocusable: true + notFocusable: true, + inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, + inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, + inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, })); this._register(this.toggle.onChange(() => this._action.checked = !!this.toggle && this.toggle.checked)); } @@ -106,7 +109,7 @@ export class Toggle extends Widget { constructor(opts: IToggleOpts) { super(); - this._opts = { ...defaultOpts, ...opts }; + this._opts = opts; this._checked = this._opts.isChecked; const classes = ['monaco-custom-toggle']; @@ -191,24 +194,11 @@ export class Toggle extends Widget { return 2 /*margin left*/ + 2 /*border*/ + 2 /*padding*/ + 16 /* icon width */; } - style(styles: IToggleStyles): void { - if (styles.inputActiveOptionBorder) { - this._opts.inputActiveOptionBorder = styles.inputActiveOptionBorder; - } - if (styles.inputActiveOptionForeground) { - this._opts.inputActiveOptionForeground = styles.inputActiveOptionForeground; - } - if (styles.inputActiveOptionBackground) { - this._opts.inputActiveOptionBackground = styles.inputActiveOptionBackground; - } - this.applyStyles(); - } - protected applyStyles(): void { if (this.domNode) { - this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : ''; - this.domNode.style.color = this._checked && this._opts.inputActiveOptionForeground ? this._opts.inputActiveOptionForeground.toString() : 'inherit'; - this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : ''; + this.domNode.style.borderColor = (this._checked && this._opts.inputActiveOptionBorder) || ''; + this.domNode.style.color = (this._checked && this._opts.inputActiveOptionForeground) || 'inherit'; + this.domNode.style.backgroundColor = (this._checked && this._opts.inputActiveOptionBackground) || ''; } } @@ -232,18 +222,16 @@ export class Checkbox extends Widget { readonly domNode: HTMLElement; - constructor(private title: string, private isChecked: boolean) { + constructor(private title: string, private isChecked: boolean, styles: ICheckboxStyles) { super(); - this.checkbox = new Toggle({ title: this.title, isChecked: this.isChecked, icon: Codicon.check, actionClassName: 'monaco-checkbox' }); + this.checkbox = new Toggle({ title: this.title, isChecked: this.isChecked, icon: Codicon.check, actionClassName: 'monaco-checkbox', ...unthemedToggleStyles }); this.domNode = this.checkbox.domNode; - this.styles = {}; + this.styles = styles; - this.checkbox.onChange(() => { - this.applyStyles(); - }); + this.applyStyles(); } get checked(): boolean { @@ -252,8 +240,6 @@ export class Checkbox extends Widget { set checked(newIsChecked: boolean) { this.checkbox.checked = newIsChecked; - - this.applyStyles(); } focus(): void { @@ -264,15 +250,9 @@ export class Checkbox extends Widget { return this.domNode === document.activeElement; } - style(styles: ICheckboxStyles): void { - this.styles = styles; - - this.applyStyles(); - } - protected applyStyles(): void { - this.domNode.style.color = this.styles.checkboxForeground ? this.styles.checkboxForeground.toString() : ''; - this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : ''; - this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : ''; + this.domNode.style.color = this.styles.checkboxForeground || ''; + this.domNode.style.backgroundColor = this.styles.checkboxBackground || ''; + this.domNode.style.borderColor = this.styles.checkboxBorder || ''; } } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index b9232d29cd6..8298616b75e 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -9,12 +9,12 @@ import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; -import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; +import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; @@ -22,7 +22,6 @@ import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays'; import { disposableTimeout, timeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { SetMap } from 'vs/base/common/collections'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -644,27 +643,45 @@ class FindFilter implements ITreeFilter, IDi export interface ICaseSensitiveToggleOpts { readonly isChecked: boolean; - readonly inputActiveOptionBorder?: Color; - readonly inputActiveOptionForeground?: Color; - readonly inputActiveOptionBackground?: Color; + readonly inputActiveOptionBorder: string | undefined; + readonly inputActiveOptionForeground: string | undefined; + readonly inputActiveOptionBackground: string | undefined; } export class ModeToggle extends Toggle { - constructor(opts?: ICaseSensitiveToggleOpts) { + constructor(opts: ICaseSensitiveToggleOpts) { super({ icon: Codicon.listFilter, title: localize('filter', "Filter"), - isChecked: opts?.isChecked ?? false, - inputActiveOptionBorder: opts?.inputActiveOptionBorder, - inputActiveOptionForeground: opts?.inputActiveOptionForeground, - inputActiveOptionBackground: opts?.inputActiveOptionBackground + isChecked: opts.isChecked ?? false, + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionForeground: opts.inputActiveOptionForeground, + inputActiveOptionBackground: opts.inputActiveOptionBackground }); } } -export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { } +export interface IFindWidgetStyles { + listFilterWidgetBackground: string | undefined; + listFilterWidgetOutline: string | undefined; + listFilterWidgetNoMatchesOutline: string | undefined; + listFilterWidgetShadow: string | undefined; + readonly toggleStyles: IToggleStyles; + readonly inputBoxStyles: IInputBoxStyles; +} -export interface IFindWidgetOpts extends IFindWidgetStyles { } +export interface IFindWidgetOptions { + readonly styles?: IFindWidgetStyles; +} + +const unthemedFindWidgetStyles: IFindWidgetStyles = { + inputBoxStyles: unthemedInboxStyles, + toggleStyles: unthemedToggleStyles, + listFilterWidgetBackground: undefined, + listFilterWidgetNoMatchesOutline: undefined, + listFilterWidgetOutline: undefined, + listFilterWidgetShadow: undefined +}; export enum TreeFindMode { Highlight, @@ -709,20 +726,32 @@ class FindWidget extends Disposable { private tree: AbstractTree, contextViewProvider: IContextViewProvider, mode: TreeFindMode, - options?: IFindWidgetOpts + options?: IFindWidgetOptions ) { super(); container.appendChild(this.elements.root); this._register(toDisposable(() => container.removeChild(this.elements.root))); - this.modeToggle = this._register(new ModeToggle({ ...options, isChecked: mode === TreeFindMode.Filter })); + const styles = options?.styles ?? unthemedFindWidgetStyles; + + if (styles.listFilterWidgetBackground) { + this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground; + } + + if (styles.listFilterWidgetShadow) { + this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; + } + + this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter })); this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, { label: localize('type to search', "Type to search"), additionalToggles: [this.modeToggle], - showCommonFindToggles: false + showCommonFindToggles: false, + inputBoxStyles: styles.inputBoxStyles, + toggleStyles: styles.toggleStyles })); this.actionbar = this._register(new ActionBar(this.elements.actionbar)); @@ -822,19 +851,6 @@ class FindWidget extends Disposable { })); this.onDidChangeValue = this.findInput.onDidChange; - this.style(options ?? {}); - } - - style(styles: IFindWidgetStyles): void { - this.findInput.style(styles); - - if (styles.listFilterWidgetBackground) { - this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString(); - } - - if (styles.listFilterWidgetShadow) { - this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; - } } focus() { @@ -869,6 +885,8 @@ class FindWidget extends Disposable { } } +interface IFindControllerOptions extends IFindWidgetOptions { } + class FindController implements IDisposable { private _pattern = ''; @@ -894,7 +912,6 @@ class FindController implements IDisposable { } private widget: FindWidget | undefined; - private styles: IFindWidgetStyles | undefined; private width = 0; private readonly _onDidChangeMode = new Emitter(); @@ -914,7 +931,8 @@ class FindController implements IDisposable { model: ITreeModel, private view: List>, private filter: FindFilter, - private readonly contextViewProvider: IContextViewProvider + private readonly contextViewProvider: IContextViewProvider, + private readonly options: IFindControllerOptions = {} ) { this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight; model.onDidSplice(this.onDidSpliceModel, this, this.disposables); @@ -928,7 +946,7 @@ class FindController implements IDisposable { } this.mode = this.tree.options.defaultFindMode ?? TreeFindMode.Highlight; - this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.styles); + this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.options); this.enabledDisposables.add(this.widget); this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables); @@ -1020,11 +1038,6 @@ class FindController implements IDisposable { return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore); } - style(styles: IFindWidgetStyles): void { - this.styles = styles; - this.widget?.style(styles); - } - layout(width: number): void { this.width = width; this.widget?.layout(width); @@ -1084,6 +1097,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly dnd?: ITreeDragAndDrop; readonly additionalScrollHeight?: number; readonly findWidgetEnabled?: boolean; + readonly findWidgetStyles?: IFindWidgetStyles; } function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { @@ -1500,7 +1514,8 @@ export abstract class AbstractTree implements IDisposable } if ((_options.findWidgetEnabled ?? true) && _options.keyboardNavigationLabelProvider && _options.contextViewProvider) { - this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider); + const opts = this.options.findWidgetStyles ? { styles: this.options.findWidgetStyles } : undefined; + this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider, opts); this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node); this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState; this.disposables.add(this.findController!); @@ -1630,7 +1645,6 @@ export abstract class AbstractTree implements IDisposable this.styleElement.textContent = content.join('\n'); - this.findController?.style(styles); this.view.style(styles); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 63d1d9e0113..063e38474c8 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -268,8 +268,7 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : ( e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) - ), - additionalScrollHeight: options.additionalScrollHeight + ) }; } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index f2f31a4cb6c..f0a8d484126 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -15,7 +15,7 @@ import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybi import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; import { IProgressBarStyles, ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Action } from 'vs/base/common/actions'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; @@ -56,6 +56,7 @@ export interface IQuickInputOptions { export interface IQuickInputStyles { widget: IQuickInputWidgetStyles; inputBox: IInputBoxStyles; + toggle: IToggleStyles; countBadge: ICountBadgetyles; button: IButtonStyles; progressBar: IProgressBarStyles; @@ -1282,7 +1283,7 @@ export class QuickInputController extends Disposable { const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); const filterContainer = dom.append(extraContainer, $('.quick-input-filter')); - const inputBox = this._register(new QuickInputBox(filterContainer)); + const inputBox = this._register(new QuickInputBox(filterContainer, this.styles.inputBox, this.styles.toggle)); inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); @@ -1823,7 +1824,6 @@ export class QuickInputController extends Disposable { this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : ''; this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : ''; this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : ''; - this.ui.inputBox.style(this.styles.inputBox); this.ui.count.style(this.styles.countBadge); this.ui.list.style(this.styles.list); diff --git a/src/vs/base/parts/quickinput/browser/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts index 1184a456574..f73ff7d739a 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputBox.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputBox.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { IInputBoxStyles, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import 'vs/css!./media/quickInput'; @@ -21,11 +21,13 @@ export class QuickInputBox extends Disposable { private findInput: FindInput; constructor( - private parent: HTMLElement + private parent: HTMLElement, + inputBoxStyles: IInputBoxStyles, + toggleStyles: IToggleStyles ) { super(); this.container = dom.append(this.parent, $('.quick-input-box')); - this.findInput = this._register(new FindInput(this.container, undefined, { label: '' })); + this.findInput = this._register(new FindInput(this.container, undefined, { label: '', inputBoxStyles, toggleStyles })); } onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => { @@ -127,8 +129,4 @@ export class QuickInputBox extends Disposable { layout(): void { this.findInput.inputBox.layout(); } - - style(styles: IInputBoxStyles): void { - this.findInput.style(styles); - } } diff --git a/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts b/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts index b408d42236c..f5c04572ec8 100644 --- a/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts @@ -4,9 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { unthemedButtonStyles } from 'vs/base/browser/ui/button/button'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListOptions, List } from 'vs/base/browser/ui/list/listWidget'; +import { unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { raceTimeout } from 'vs/base/common/async'; import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput'; import { IQuickPick, IQuickPickItem } from 'vs/base/parts/quickinput/common/quickInput'; @@ -55,7 +57,8 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 styles: { button: unthemedButtonStyles, countBadge: {}, - inputBox: {}, + inputBox: unthemedInboxStyles, + toggle: unthemedToggleStyles, keybindingLabel: {}, list: {}, progressBar: {}, diff --git a/src/vs/base/test/browser/ui/tree/dataTree.test.ts b/src/vs/base/test/browser/ui/tree/dataTree.test.ts index bc6ed5f3106..ab450c682e7 100644 --- a/src/vs/base/test/browser/ui/tree/dataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/dataTree.test.ts @@ -63,9 +63,7 @@ suite('DataTree', function () { } }; - tree = new DataTree('test', container, delegate, [renderer], dataSource, { - identityProvider - }); + tree = new DataTree('test', container, delegate, [renderer], dataSource, { identityProvider }); tree.layout(200); }); diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 2fcf59e3ae0..f71559a35dd 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -333,7 +333,7 @@ class ProcessExplorer { return 'header'; } - }, + } }); this.tree.setInput({ processes: { processRoots } }); diff --git a/src/vs/editor/contrib/find/browser/findController.ts b/src/vs/editor/contrib/find/browser/findController.ts index 3ca56c29d04..676fa5dcbaa 100644 --- a/src/vs/editor/contrib/find/browser/findController.ts +++ b/src/vs/editor/contrib/find/browser/findController.ts @@ -484,7 +484,7 @@ export class FindController extends CommonFindController implements IFindControl private _createFindWidget() { this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService)); - this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService)); + this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService)); } saveViewState(): any { diff --git a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts index 0246d79df8e..096af99392b 100644 --- a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts @@ -12,8 +12,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPosit import { FIND_IDS } from 'vs/editor/contrib/find/browser/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { asCssValue, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -31,8 +30,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { constructor( editor: ICodeEditor, state: FindReplaceState, - keybindingService: IKeybindingService, - themeService: IThemeService + keybindingService: IKeybindingService ) { super(); @@ -48,16 +46,16 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('role', 'presentation'); this._domNode.setAttribute('aria-hidden', 'true'); - const inputActiveOptionBorderColor = themeService.getColorTheme().getColor(inputActiveOptionBorder); - const inputActiveOptionForegroundColor = themeService.getColorTheme().getColor(inputActiveOptionForeground); - const inputActiveOptionBackgroundColor = themeService.getColorTheme().getColor(inputActiveOptionBackground); + const toggleStyles = { + inputActiveOptionBorder: asCssValue(inputActiveOptionBorder), + inputActiveOptionForeground: asCssValue(inputActiveOptionForeground), + inputActiveOptionBackground: asCssValue(inputActiveOptionBackground), + }; this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), isChecked: this._state.matchCase, - inputActiveOptionBorder: inputActiveOptionBorderColor, - inputActiveOptionForeground: inputActiveOptionForegroundColor, - inputActiveOptionBackground: inputActiveOptionBackgroundColor + ...toggleStyles })); this._domNode.appendChild(this.caseSensitive.domNode); this._register(this.caseSensitive.onChange(() => { @@ -69,9 +67,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), isChecked: this._state.wholeWord, - inputActiveOptionBorder: inputActiveOptionBorderColor, - inputActiveOptionForeground: inputActiveOptionForegroundColor, - inputActiveOptionBackground: inputActiveOptionBackgroundColor + ...toggleStyles })); this._domNode.appendChild(this.wholeWords.domNode); this._register(this.wholeWords.onChange(() => { @@ -83,9 +79,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.regex = this._register(new RegexToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), isChecked: this._state.isRegex, - inputActiveOptionBorder: inputActiveOptionBorderColor, - inputActiveOptionForeground: inputActiveOptionForegroundColor, - inputActiveOptionBackground: inputActiveOptionBackgroundColor + ...toggleStyles })); this._domNode.appendChild(this.regex.domNode); this._register(this.regex.onChange(() => { @@ -117,9 +111,6 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._register(dom.addDisposableListener(this._domNode, dom.EventType.MOUSE_LEAVE, (e) => this._onMouseLeave())); this._register(dom.addDisposableListener(this._domNode, 'mouseover', (e) => this._onMouseOver())); - - this._applyTheme(themeService.getColorTheme()); - this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); } private _keybindingLabelFor(actionId: string): string { @@ -187,15 +178,4 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._isVisible = false; this._domNode.style.display = 'none'; } - - private _applyTheme(theme: IColorTheme) { - const inputStyles = { - inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), - inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground), - inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground) - }; - this.caseSensitive.style(inputStyles); - this.wholeWords.style(inputStyles); - this.regex.style(inputStyles); - } } diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 72b7ad349e8..5d4b9f76f63 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -9,7 +9,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; @@ -36,11 +36,12 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, editorWidgetResizeBorder, errorForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, toolbarHoverBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { asCssValue, contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, editorWidgetResizeBorder, errorForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, toolbarHoverBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; -import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { assertIsDefined } from 'vs/base/common/types'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.')); const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); @@ -258,9 +259,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line. } - this._applyTheme(themeService.getColorTheme()); - this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); - this._register(this._codeEditor.onDidChangeModel(() => { if (!this._isVisible) { return; @@ -675,29 +673,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL }); } - private _applyTheme(theme: IColorTheme) { - const inputStyles: IFindInputStyles = { - inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), - inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), - inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground), - inputBackground: theme.getColor(inputBackground), - inputForeground: theme.getColor(inputForeground), - inputBorder: theme.getColor(inputBorder), - inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), - inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), - inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), - inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), - inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), - inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), - inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), - }; - this._findInput.style(inputStyles); - this._replaceInput.style(inputStyles); - this._toggleSelectionFind.style(inputStyles); - } - private _tryUpdateWidgetWidth() { if (!this._isVisible) { return; @@ -981,7 +956,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL flexibleWidth, flexibleMaxHeight: 118, showCommonFindToggles: true, - showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService) + showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService), + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles }, this._contextKeyService)); this._findInput.setRegex(!!this._state.isRegex); this._findInput.setCaseSensitive(!!this._state.matchCase); @@ -1061,7 +1038,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._toggleSelectionFind = this._register(new Toggle({ icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), - isChecked: false + isChecked: false, + inputActiveOptionBackground: asCssValue(inputActiveOptionBackground), + inputActiveOptionBorder: asCssValue(inputActiveOptionBorder), + inputActiveOptionForeground: asCssValue(inputActiveOptionForeground), })); this._register(this._toggleSelectionFind.onChange(() => { @@ -1121,7 +1101,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL flexibleHeight, flexibleWidth, flexibleMaxHeight: 118, - showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService) + showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService), + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles }, this._contextKeyService, true)); this._replaceInput.setPreserveCase(!!this._state.preserveCase); this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e))); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 180b544463f..95467136e67 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -27,6 +27,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; +import { defaultFindWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -1123,7 +1124,8 @@ function workbenchTreeDataPreamble(treeExpandMode) === 'doubleClick'), - contextViewProvider: contextViewService as IContextViewProvider + contextViewProvider: contextViewService as IContextViewProvider, + findWidgetStyles: defaultFindWidgetStyles } as TOptions }; } diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 6210a38dad6..372a4a4e971 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -16,8 +16,8 @@ import { IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/l import { QuickAccessController } from 'vs/platform/quickinput/browser/quickAccess'; import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { defaultButtonStyles, getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, pickerGroupBorder, pickerGroupForeground, quickInputBackground, quickInputForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, quickInputTitleBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { defaultButtonStyles, defaultInputBoxStyles, defaultKeybindingLabelStyles, defaultProgressBarStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputBackground, quickInputForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, quickInputTitleBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { computeStyles } from 'vs/platform/theme/common/styler'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; @@ -194,34 +194,16 @@ export class QuickInputService extends Themable implements IQuickInputService { widgetShadow }), }, - inputBox: computeStyles(this.theme, { - inputForeground, - inputBackground, - inputBorder, - inputValidationInfoBackground, - inputValidationInfoForeground, - inputValidationInfoBorder, - inputValidationWarningBackground, - inputValidationWarningForeground, - inputValidationWarningBorder, - inputValidationErrorBackground, - inputValidationErrorForeground, - inputValidationErrorBorder - }), + inputBox: defaultInputBoxStyles, + toggle: defaultToggleStyles, countBadge: computeStyles(this.theme, { badgeBackground, badgeForeground, badgeBorder: contrastBorder }), button: defaultButtonStyles, - progressBar: getProgressBarStyles(), // default uses progressBarBackground - keybindingLabel: computeStyles(this.theme, { - keybindingLabelBackground, - keybindingLabelForeground, - keybindingLabelBorder, - keybindingLabelBottomBorder, - keybindingLabelShadow: widgetShadow - }), + progressBar: defaultProgressBarStyles, // default uses progressBarBackground + keybindingLabel: defaultKeybindingLabelStyles, list: computeStyles(this.theme, { listBackground: quickInputBackground, // Look like focused when inactive. diff --git a/src/vs/platform/theme/browser/defaultStyles.ts b/src/vs/platform/theme/browser/defaultStyles.ts index 5220a7c0263..c3e30709514 100644 --- a/src/vs/platform/theme/browser/defaultStyles.ts +++ b/src/vs/platform/theme/browser/defaultStyles.ts @@ -4,62 +4,112 @@ *--------------------------------------------------------------------------------------------*/ import { IButtonStyles } from 'vs/base/browser/ui/button/button'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssValue, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssValue, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IProgressBarStyles } from 'vs/base/browser/ui/progressbar/progressbar'; -import { IStyleOverrides } from 'vs/platform/theme/common/styler'; +import { ICheckboxStyles, IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; +import { IDialogStyles } from 'vs/base/browser/ui/dialog/dialog'; +import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IFindWidgetStyles } from 'vs/base/browser/ui/tree/abstractTree'; +export type IStyleOverride = { + [P in keyof T]?: ColorIdentifier; +}; -export interface IKeybindingLabelStyleOverrides extends IStyleOverrides { - keybindingLabelBackground?: ColorIdentifier; - keybindingLabelForeground?: ColorIdentifier; - keybindingLabelBorder?: ColorIdentifier; - keybindingLabelBottomBorder?: ColorIdentifier; - keybindingLabelShadow?: ColorIdentifier; -} +export const defaultKeybindingLabelStyles = getKeybindingLabelStyles({}); -export function getKeybindingLabelStyles(style?: IKeybindingLabelStyleOverrides): IKeybindingLabelStyles { +export function getKeybindingLabelStyles(override: IStyleOverride): IKeybindingLabelStyles { return { - keybindingLabelBackground: asCssValue(style?.keybindingLabelBackground || keybindingLabelBackground), - keybindingLabelForeground: asCssValue(style?.keybindingLabelForeground || keybindingLabelForeground), - keybindingLabelBorder: asCssValue(style?.keybindingLabelBorder || keybindingLabelBorder), - keybindingLabelBottomBorder: asCssValue(style?.keybindingLabelBottomBorder || keybindingLabelBottomBorder), - keybindingLabelShadow: asCssValue(style?.keybindingLabelShadow || widgetShadow) + keybindingLabelBackground: asCssValue(override.keybindingLabelBackground ?? keybindingLabelBackground), + keybindingLabelForeground: asCssValue(override.keybindingLabelForeground ?? keybindingLabelForeground), + keybindingLabelBorder: asCssValue(override.keybindingLabelBorder ?? keybindingLabelBorder), + keybindingLabelBottomBorder: asCssValue(override.keybindingLabelBottomBorder ?? keybindingLabelBottomBorder), + keybindingLabelShadow: asCssValue(override.keybindingLabelShadow ?? widgetShadow) }; } - -export interface IButtonStyleOverrides extends IStyleOverrides { - readonly buttonForeground?: ColorIdentifier; - readonly buttonSeparator?: ColorIdentifier; - readonly buttonBackground?: ColorIdentifier; - readonly buttonHoverBackground?: ColorIdentifier; - readonly buttonSecondaryForeground?: ColorIdentifier; - readonly buttonSecondaryBackground?: ColorIdentifier; - readonly buttonSecondaryHoverBackground?: ColorIdentifier; - readonly buttonBorder?: ColorIdentifier; -} - - export const defaultButtonStyles: IButtonStyles = getButtonStyles({}); -export function getButtonStyles(style: IButtonStyleOverrides): IButtonStyles { +export function getButtonStyles(override: IStyleOverride): IButtonStyles { return { - buttonForeground: asCssValue(style.buttonForeground || buttonForeground), - buttonSeparator: asCssValue(style.buttonSeparator || buttonSeparator), - buttonBackground: asCssValue(style.buttonBackground || buttonBackground), - buttonHoverBackground: asCssValue(style.buttonHoverBackground || buttonHoverBackground), - buttonSecondaryForeground: asCssValue(style.buttonSecondaryForeground || buttonSecondaryForeground), - buttonSecondaryBackground: asCssValue(style.buttonSecondaryBackground || buttonSecondaryBackground), - buttonSecondaryHoverBackground: asCssValue(style.buttonSecondaryHoverBackground || buttonSecondaryHoverBackground), - buttonBorder: asCssValue(style.buttonBorder || buttonBorder), + buttonForeground: asCssValue(override.buttonForeground ?? buttonForeground), + buttonSeparator: asCssValue(override.buttonSeparator ?? buttonSeparator), + buttonBackground: asCssValue(override.buttonBackground ?? buttonBackground), + buttonHoverBackground: asCssValue(override.buttonHoverBackground ?? buttonHoverBackground), + buttonSecondaryForeground: asCssValue(override.buttonSecondaryForeground ?? buttonSecondaryForeground), + buttonSecondaryBackground: asCssValue(override.buttonSecondaryBackground ?? buttonSecondaryBackground), + buttonSecondaryHoverBackground: asCssValue(override.buttonSecondaryHoverBackground ?? buttonSecondaryHoverBackground), + buttonBorder: asCssValue(override.buttonBorder ?? buttonBorder), }; } -export interface IProgressBarStyleOverrides extends IStyleOverrides { - progressBarBackground?: ColorIdentifier; -} +export const defaultProgressBarStyles: IProgressBarStyles = getProgressBarStyles({}); -export function getProgressBarStyles(style?: IProgressBarStyleOverrides): IProgressBarStyles { +export function getProgressBarStyles(override: IStyleOverride): IProgressBarStyles { return { - progressBarBackground: asCssValue(style?.progressBarBackground || progressBarBackground) + progressBarBackground: asCssValue(override.progressBarBackground ?? progressBarBackground) }; } + +export const defaultToggleStyles: IToggleStyles = getToggleStyles({}); + +export function getToggleStyles(override: IStyleOverride): IToggleStyles { + return { + inputActiveOptionBorder: asCssValue(override.inputActiveOptionBorder ?? inputActiveOptionBorder), + inputActiveOptionForeground: asCssValue(override.inputActiveOptionForeground ?? inputActiveOptionForeground), + inputActiveOptionBackground: asCssValue(override.inputActiveOptionBackground ?? inputActiveOptionBackground) + }; +} + +export const defaultCheckboxStyles: ICheckboxStyles = getCheckboxStyles({}); + +export function getCheckboxStyles(override: IStyleOverride): ICheckboxStyles { + return { + checkboxBackground: asCssValue(override.checkboxBackground ?? checkboxBackground), + checkboxBorder: asCssValue(override.checkboxBorder ?? checkboxBorder), + checkboxForeground: asCssValue(override.checkboxForeground ?? checkboxForeground) + }; +} + +export type IDialogStyleOverrides = IStyleOverride; + +export const defaultDialogStyles = getDialogStyle({}); + +export function getDialogStyle(override: IStyleOverride): IDialogStyles { + return { + dialogBackground: asCssValue(override.dialogBackground ?? editorWidgetBackground), + dialogForeground: asCssValue(override.dialogForeground ?? editorWidgetForeground), + dialogShadow: asCssValue(override.dialogShadow ?? widgetShadow), + dialogBorder: asCssValue(override.dialogBorder ?? contrastBorder), + errorIconForeground: asCssValue(override.errorIconForeground ?? problemsErrorIconForeground), + warningIconForeground: asCssValue(override.warningIconForeground ?? problemsWarningIconForeground), + infoIconForeground: asCssValue(override.infoIconForeground ?? problemsInfoIconForeground), + textLinkForeground: asCssValue(override.textLinkForeground ?? textLinkForeground) + }; +} + +export const defaultInputBoxStyles = getInputBoxStyle({}); + +export function getInputBoxStyle(override: IStyleOverride): IInputBoxStyles { + return { + inputBackground: asCssValue(override.inputBackground ?? inputBackground), + inputForeground: asCssValue(override.inputForeground ?? inputForeground), + inputBorder: asCssValue(override.inputBorder ?? inputBorder), + inputValidationInfoBorder: asCssValue(override.inputValidationInfoBorder ?? inputValidationInfoBorder), + inputValidationInfoBackground: asCssValue(override.inputValidationInfoBackground ?? inputValidationInfoBackground), + inputValidationInfoForeground: asCssValue(override.inputValidationInfoForeground ?? inputValidationInfoForeground), + inputValidationWarningBorder: asCssValue(override.inputValidationWarningBorder ?? inputValidationWarningBorder), + inputValidationWarningBackground: asCssValue(override.inputValidationWarningBackground ?? inputValidationWarningBackground), + inputValidationWarningForeground: asCssValue(override.inputValidationWarningForeground ?? inputValidationWarningForeground), + inputValidationErrorBorder: asCssValue(override.inputValidationErrorBorder ?? inputValidationErrorBorder), + inputValidationErrorBackground: asCssValue(override.inputValidationErrorBackground ?? inputValidationErrorBackground), + inputValidationErrorForeground: asCssValue(override.inputValidationErrorForeground ?? inputValidationErrorForeground) + }; +} + +export const defaultFindWidgetStyles: IFindWidgetStyles = { + listFilterWidgetBackground: asCssValue(listFilterWidgetBackground), + listFilterWidgetOutline: asCssValue(listFilterWidgetOutline), + listFilterWidgetNoMatchesOutline: asCssValue(listFilterWidgetNoMatchesOutline), + listFilterWidgetShadow: asCssValue(listFilterWidgetShadow), + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles +}; diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index e9ab3704a2f..203b79689b4 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -247,7 +247,7 @@ export const inputBorder = registerColor('input.border', { dark: null, light: nu export const inputActiveOptionBorder = registerColor('inputOption.activeBorder', { dark: '#007ACC00', light: '#007ACC00', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('inputBoxActiveOptionBorder', "Border color of activated options in input fields.")); export const inputActiveOptionHoverBackground = registerColor('inputOption.hoverBackground', { dark: '#5a5d5e80', light: '#b8b8b850', hcDark: null, hcLight: null }, nls.localize('inputOption.hoverBackground', "Background color of activated options in input fields.")); export const inputActiveOptionBackground = registerColor('inputOption.activeBackground', { dark: transparent(focusBorder, 0.4), light: transparent(focusBorder, 0.2), hcDark: Color.transparent, hcLight: Color.transparent }, nls.localize('inputOption.activeBackground', "Background hover color of options in input fields.")); -export const inputActiveOptionForeground = registerColor('inputOption.activeForeground', { dark: Color.white, light: Color.black, hcDark: null, hcLight: foreground }, nls.localize('inputOption.activeForeground', "Foreground color of activated options in input fields.")); +export const inputActiveOptionForeground = registerColor('inputOption.activeForeground', { dark: Color.white, light: Color.black, hcDark: foreground, hcLight: foreground }, nls.localize('inputOption.activeForeground', "Foreground color of activated options in input fields.")); export const inputPlaceholderForeground = registerColor('input.placeholderForeground', { light: transparent(foreground, 0.5), dark: transparent(foreground, 0.5), hcDark: transparent(foreground, 0.7), hcLight: transparent(foreground, 0.7) }, nls.localize('inputPlaceholderForeground', "Input box foreground color for placeholder text.")); export const inputValidationInfoBackground = registerColor('inputValidation.infoBackground', { dark: '#063B49', light: '#D6ECF2', hcDark: Color.black, hcLight: Color.white }, nls.localize('inputValidationInfoBackground', "Input validation background color for information severity.")); @@ -268,7 +268,7 @@ export const selectBorder = registerColor('dropdown.border', { dark: selectBackg export const buttonForeground = registerColor('button.foreground', { dark: Color.white, light: Color.white, hcDark: Color.white, hcLight: Color.white }, nls.localize('buttonForeground', "Button foreground color.")); export const buttonSeparator = registerColor('button.separator', { dark: transparent(buttonForeground, .4), light: transparent(buttonForeground, .4), hcDark: transparent(buttonForeground, .4), hcLight: transparent(buttonForeground, .4) }, nls.localize('buttonSeparator', "Button separator color.")); export const buttonBackground = registerColor('button.background', { dark: '#0E639C', light: '#007ACC', hcDark: null, hcLight: '#0F4A85' }, nls.localize('buttonBackground', "Button background color.")); -export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hcDark: null, hcLight: null }, nls.localize('buttonHoverBackground', "Button background color when hovering.")); +export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hcDark: buttonBackground, hcLight: buttonBackground }, nls.localize('buttonHoverBackground', "Button background color when hovering.")); export const buttonBorder = registerColor('button.border', { dark: contrastBorder, light: contrastBorder, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('buttonBorder', "Button border color.")); export const buttonSecondaryForeground = registerColor('button.secondaryForeground', { dark: Color.white, light: Color.white, hcDark: Color.white, hcLight: foreground }, nls.localize('buttonSecondaryForeground', "Secondary button foreground color.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index d1c8fc7594c..503c803501c 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -6,7 +6,19 @@ import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IThemable, styleFn } from 'vs/base/common/styler'; -import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, checkboxBackground, checkboxBorder, checkboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow, listFocusAndSelectionOutline, listFilterWidgetShadow, buttonSeparator } from 'vs/platform/theme/common/colorRegistry'; +import { + activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, + ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBorder, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, + inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, + inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, + listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, + listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, + listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, + menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, + quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, + scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, tableColumnsBorder, tableOddRowsBackgroundColor, + treeIndentGuidesStroke, widgetShadow, listFocusAndSelectionOutline +} from 'vs/platform/theme/common/colorRegistry'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -50,20 +62,6 @@ export function attachStyler(themeService: IThemeServic return themeService.onDidColorThemeChange(applyStyles); } -export interface IToggleStyleOverrides extends IStyleOverrides { - inputActiveOptionBorderColor?: ColorIdentifier; - inputActiveOptionForegroundColor?: ColorIdentifier; - inputActiveOptionBackgroundColor?: ColorIdentifier; -} - -export function attachToggleStyler(widget: IThemable, themeService: IThemeService, style?: IToggleStyleOverrides): IDisposable { - return attachStyler(themeService, { - inputActiveOptionBorder: style?.inputActiveOptionBorderColor || inputActiveOptionBorder, - inputActiveOptionForeground: style?.inputActiveOptionForegroundColor || inputActiveOptionForeground, - inputActiveOptionBackground: style?.inputActiveOptionBackgroundColor || inputActiveOptionBackground - } as IToggleStyleOverrides, widget); -} - export interface IBadgeStyleOverrides extends IStyleOverrides { badgeBackground?: ColorIdentifier; badgeForeground?: ColorIdentifier; @@ -77,41 +75,6 @@ export function attachBadgeStyler(widget: IThemable, themeService: IThemeService } as IBadgeStyleOverrides, widget); } -export interface IInputBoxStyleOverrides extends IStyleOverrides { - inputBackground?: ColorIdentifier; - inputForeground?: ColorIdentifier; - inputBorder?: ColorIdentifier; - inputActiveOptionBorder?: ColorIdentifier; - inputActiveOptionForeground?: ColorIdentifier; - inputActiveOptionBackground?: ColorIdentifier; - inputValidationInfoBorder?: ColorIdentifier; - inputValidationInfoBackground?: ColorIdentifier; - inputValidationInfoForeground?: ColorIdentifier; - inputValidationWarningBorder?: ColorIdentifier; - inputValidationWarningBackground?: ColorIdentifier; - inputValidationWarningForeground?: ColorIdentifier; - inputValidationErrorBorder?: ColorIdentifier; - inputValidationErrorBackground?: ColorIdentifier; - inputValidationErrorForeground?: ColorIdentifier; -} - -export function attachInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { - return attachStyler(themeService, { - inputBackground: style?.inputBackground || inputBackground, - inputForeground: style?.inputForeground || inputForeground, - inputBorder: style?.inputBorder || inputBorder, - inputValidationInfoBorder: style?.inputValidationInfoBorder || inputValidationInfoBorder, - inputValidationInfoBackground: style?.inputValidationInfoBackground || inputValidationInfoBackground, - inputValidationInfoForeground: style?.inputValidationInfoForeground || inputValidationInfoForeground, - inputValidationWarningBorder: style?.inputValidationWarningBorder || inputValidationWarningBorder, - inputValidationWarningBackground: style?.inputValidationWarningBackground || inputValidationWarningBackground, - inputValidationWarningForeground: style?.inputValidationWarningForeground || inputValidationWarningForeground, - inputValidationErrorBorder: style?.inputValidationErrorBorder || inputValidationErrorBorder, - inputValidationErrorBackground: style?.inputValidationErrorBackground || inputValidationErrorBackground, - inputValidationErrorForeground: style?.inputValidationErrorForeground || inputValidationErrorForeground - } as IInputBoxStyleOverrides, widget); -} - export interface ISelectBoxStyleOverrides extends IStyleOverrides, IListStyleOverrides { selectBackground?: ColorIdentifier; selectListBackground?: ColorIdentifier; @@ -140,26 +103,6 @@ export function attachSelectBoxStyler(widget: IThemable, themeService: IThemeSer } as ISelectBoxStyleOverrides, widget); } -export function attachFindReplaceInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { - return attachStyler(themeService, { - inputBackground: style?.inputBackground || inputBackground, - inputForeground: style?.inputForeground || inputForeground, - inputBorder: style?.inputBorder || inputBorder, - inputActiveOptionBorder: style?.inputActiveOptionBorder || inputActiveOptionBorder, - inputActiveOptionForeground: style?.inputActiveOptionForeground || inputActiveOptionForeground, - inputActiveOptionBackground: style?.inputActiveOptionBackground || inputActiveOptionBackground, - inputValidationInfoBorder: style?.inputValidationInfoBorder || inputValidationInfoBorder, - inputValidationInfoBackground: style?.inputValidationInfoBackground || inputValidationInfoBackground, - inputValidationInfoForeground: style?.inputValidationInfoForeground || inputValidationInfoForeground, - inputValidationWarningBorder: style?.inputValidationWarningBorder || inputValidationWarningBorder, - inputValidationWarningBackground: style?.inputValidationWarningBackground || inputValidationWarningBackground, - inputValidationWarningForeground: style?.inputValidationWarningForeground || inputValidationWarningForeground, - inputValidationErrorBorder: style?.inputValidationErrorBorder || inputValidationErrorBorder, - inputValidationErrorBackground: style?.inputValidationErrorBackground || inputValidationErrorBackground, - inputValidationErrorForeground: style?.inputValidationErrorForeground || inputValidationErrorForeground - } as IInputBoxStyleOverrides, widget); -} - export interface IListStyleOverrides extends IStyleOverrides { listBackground?: ColorIdentifier; listFocusBackground?: ColorIdentifier; @@ -181,10 +124,6 @@ export interface IListStyleOverrides extends IStyleOverrides { listDropBackground?: ColorIdentifier; listSelectionOutline?: ColorIdentifier; listHoverOutline?: ColorIdentifier; - listFilterWidgetBackground?: ColorIdentifier; - listFilterWidgetOutline?: ColorIdentifier; - listFilterWidgetNoMatchesOutline?: ColorIdentifier; - listFilterWidgetShadow?: ColorIdentifier; treeIndentGuidesStroke?: ColorIdentifier; tableColumnsBorder?: ColorIdentifier; tableOddRowsBackgroundColor?: ColorIdentifier; @@ -214,10 +153,6 @@ export const defaultListStyles: IColorMapping = { listDropBackground, listSelectionOutline: activeContrastBorder, listHoverOutline: activeContrastBorder, - listFilterWidgetBackground, - listFilterWidgetOutline, - listFilterWidgetNoMatchesOutline, - listFilterWidgetShadow, treeIndentGuidesStroke, tableColumnsBorder, tableOddRowsBackgroundColor, @@ -292,60 +227,3 @@ export const defaultMenuStyles = { export function attachMenuStyler(widget: IThemable, themeService: IThemeService, style?: IMenuStyleOverrides): IDisposable { return attachStyler(themeService, { ...defaultMenuStyles, ...style }, widget); } - -interface IButtonStyleOverrides extends IStyleOverrides { - buttonForeground?: ColorIdentifier; - buttonSeparator?: ColorIdentifier; - buttonBackground?: ColorIdentifier; - buttonHoverBackground?: ColorIdentifier; - buttonSecondaryForeground?: ColorIdentifier; - buttonSecondaryBackground?: ColorIdentifier; - buttonSecondaryHoverBackground?: ColorIdentifier; - buttonBorder?: ColorIdentifier; -} - -export interface IDialogStyleOverrides extends IButtonStyleOverrides { - dialogForeground?: ColorIdentifier; - dialogBackground?: ColorIdentifier; - dialogShadow?: ColorIdentifier; - dialogBorder?: ColorIdentifier; - checkboxBorder?: ColorIdentifier; - checkboxBackground?: ColorIdentifier; - checkboxForeground?: ColorIdentifier; - errorIconForeground?: ColorIdentifier; - warningIconForeground?: ColorIdentifier; - infoIconForeground?: ColorIdentifier; - inputBackground?: ColorIdentifier; - inputForeground?: ColorIdentifier; - inputBorder?: ColorIdentifier; -} - -export const defaultDialogStyles = { - dialogBackground: editorWidgetBackground, - dialogForeground: editorWidgetForeground, - dialogShadow: widgetShadow, - dialogBorder: contrastBorder, - buttonForeground: buttonForeground, - buttonSeparator: buttonSeparator, - buttonBackground: buttonBackground, - buttonSecondaryBackground: buttonSecondaryBackground, - buttonSecondaryForeground: buttonSecondaryForeground, - buttonSecondaryHoverBackground: buttonSecondaryHoverBackground, - buttonHoverBackground: buttonHoverBackground, - buttonBorder: buttonBorder, - checkboxBorder: checkboxBorder, - checkboxBackground: checkboxBackground, - checkboxForeground: checkboxForeground, - errorIconForeground: problemsErrorIconForeground, - warningIconForeground: problemsWarningIconForeground, - infoIconForeground: problemsInfoIconForeground, - inputBackground: inputBackground, - inputForeground: inputForeground, - inputBorder: inputBorder, - textLinkForeground: textLinkForeground -}; - - -export function attachDialogStyler(widget: IThemable, themeService: IThemeService, style?: IDialogStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultDialogStyles, ...style }, widget); -} diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 33abdb3e8f8..bdb4ab394e4 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -31,7 +31,7 @@ import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { AbstractProgressScope, ScopedProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface ICompositeTitleLabel { @@ -458,7 +458,7 @@ export abstract class CompositePart extends Part { override createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('.content')); - this.progressBar = this._register(new ProgressBar(contentContainer, getProgressBarStyles())); + this.progressBar = this._register(new ProgressBar(contentContainer, defaultProgressBarStyles)); this.progressBar.hide(); return contentContainer; diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index 4fa42a97578..299ac93afef 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -10,7 +10,6 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; @@ -29,7 +28,6 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @IDialogService private dialogService: IDialogService, @ILogService logService: ILogService, @ILayoutService layoutService: ILayoutService, - @IThemeService themeService: IThemeService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IProductService productService: IProductService, @@ -37,7 +35,7 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC ) { super(); - this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, instantiationService, productService, clipboardService); + this.impl = new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService); this.model = (this.dialogService as DialogService).model; diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index e7dc6440849..6a3768dd5f5 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -9,8 +9,6 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; import { Dialog, IDialogResult } from 'vs/base/browser/ui/dialog/dialog'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachDialogStyler } from 'vs/platform/theme/common/styler'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; @@ -20,7 +18,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { fromNow } from 'vs/base/common/date'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export class BrowserDialogHandler implements IDialogHandler { @@ -38,7 +36,6 @@ export class BrowserDialogHandler implements IDialogHandler { constructor( @ILogService private readonly logService: ILogService, @ILayoutService private readonly layoutService: ILayoutService, - @IThemeService private readonly themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IProductService private readonly productService: IProductService, @@ -119,11 +116,14 @@ export class BrowserDialogHandler implements IDialogHandler { checkboxLabel: checkbox?.label, checkboxChecked: checkbox?.checked, inputs, - buttonStyles: defaultButtonStyles - }); + buttonStyles: defaultButtonStyles, + checkboxStyles: defaultCheckboxStyles, + inputBoxStyles: defaultInputBoxStyles, + dialogStyles: defaultDialogStyles + } + ); dialogDisposables.add(dialog); - dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); const result = await dialog.show(); dialogDisposables.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index a33e41cc050..10ce180feaa 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -52,8 +52,8 @@ import { URI } from 'vs/base/common/uri'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isLinux, isMacintosh, isNative, isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { TrustedTelemetryValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -183,7 +183,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.appendChild(letterpressContainer); // Progress bar - this.progressBar = this._register(new ProgressBar(this.element, getProgressBarStyles())); + this.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles)); this.progressBar.hide(); // Scoped instantiation service diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 05dbe36b621..c32bbd3cf3a 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -25,7 +25,7 @@ import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown import { DomEmitter } from 'vs/base/browser/event'; import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch'; import { Event } from 'vs/base/common/event'; -import { defaultButtonStyles, getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; export class NotificationsListDelegate implements IListVirtualDelegate { @@ -257,7 +257,7 @@ export class NotificationRenderer implements IListRenderer(); readonly onDidChangeState: Event = this._onDidChangeState.event; - constructor(container: HTMLElement, private checkboxStateHandler: CheckboxStateHandler, private themeService: IThemeService) { + constructor(container: HTMLElement, private checkboxStateHandler: CheckboxStateHandler) { super(); this.checkboxContainer = container; } @@ -54,7 +53,8 @@ export class TreeItemCheckbox extends Disposable { this.toggle = new Toggle({ isChecked: node.checkbox.isChecked, title: this.createCheckboxTitle(node.checkbox), - icon: node.checkbox.isChecked ? Codicon.check : undefined + icon: node.checkbox.isChecked ? Codicon.check : undefined, + ...defaultToggleStyles }); this.toggle.domNode.classList.add(TreeItemCheckbox.checkboxClass); @@ -70,7 +70,6 @@ export class TreeItemCheckbox extends Disposable { this._register(this.toggle.onChange(() => { this.setCheckbox(node); })); - this._register(attachToggleStyler(this.toggle, this.themeService)); } } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 5f328015235..e38c4539c5b 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1247,7 +1247,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer showHistoryKeybindingHint(this.keybindingService) + showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), + inputBoxStyles: defaultInputBoxStyles })); - this._register(attachInputBoxStyler(inputBox, this.themeService)); if (this.options.text) { inputBox.value = this.options.text; } diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index e61f0fdbb46..933adaf1f9c 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -44,7 +44,7 @@ import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { FilterWidget, IFilterWidgetOptions } from 'vs/workbench/browser/parts/views/viewFilter'; import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { defaultButtonStyles, getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface IViewPaneOptions extends IPaneOptions { id: string; @@ -452,7 +452,7 @@ export abstract class ViewPane extends Pane implements IView { getProgressIndicator() { if (this.progressBar === undefined) { // Progress bar - this.progressBar = this._register(new ProgressBar(this.element, getProgressBarStyles())); + this.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles)); this.progressBar.hide(); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 016548cb48f..923da26be3b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./simpleFindWidget'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { Widget } from 'vs/base/browser/ui/widget'; import { Delayer } from 'vs/base/common/async'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -15,8 +15,6 @@ import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBo import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon, NLS_NO_RESULTS, NLS_MATCHES_LOCATION } from 'vs/editor/contrib/find/browser/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; import * as strings from 'vs/base/common/strings'; @@ -24,6 +22,7 @@ import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find (\u21C5 for history)"); @@ -88,7 +87,9 @@ export abstract class SimpleFindWidget extends Widget { appendCaseSensitiveLabel: options.appendCaseSensitiveLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindCaseSensitive) : undefined, appendRegexLabel: options.appendRegexLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindRegex) : undefined, appendWholeWordsLabel: options.appendWholeWordsLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindWholeWord) : undefined, - showHistoryHint: () => showHistoryKeybindingHint(_keybindingService) + showHistoryHint: () => showHistoryKeybindingHint(_keybindingService), + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles }, contextKeyService)); // Find History with update delayer this._updateHistoryDelayer = new Delayer(500); @@ -209,27 +210,6 @@ export abstract class SimpleFindWidget extends Widget { return this._focusTracker; } - public updateTheme(theme: IColorTheme): void { - const inputStyles: IFindInputStyles = { - inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), - inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground), - inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), - inputBackground: theme.getColor(inputBackground), - inputForeground: theme.getColor(inputForeground), - inputBorder: theme.getColor(inputBorder), - inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), - inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), - inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), - inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), - inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), - inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), - inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder) - }; - this._findInput.style(inputStyles); - } - private _getKeybinding(actionId: string): string { const kb = this._keybindingService?.lookupKeybinding(actionId); if (!kb) { diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 378180562ba..479332bd147 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -16,8 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, ExpressionContainer, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; @@ -150,7 +149,6 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index f278ea8a78d..2af2ed66341 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -37,7 +37,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -132,10 +132,10 @@ export class BreakpointsView extends ViewPane { this.list = this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), new ExceptionBreakpointsRenderer(this.menu, this.breakpointSupportsCondition, this.breakpointItemType, this.debugService), - new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService), + new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService), this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), this.instantiationService.createInstance(DataBreakpointsRenderer), - new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService, this.labelService), + new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), this.instantiationService.createInstance(InstructionBreakpointsRenderer), ], { identityProvider: { getId: (element: IEnablement) => element.getId() }, @@ -808,7 +808,6 @@ class FunctionBreakpointInputRenderer implements IListRenderer { template.updating = true; @@ -926,7 +923,6 @@ class ExceptionBreakpointInputRenderer implements IListRenderer { this.view.breakpointInputFocused.set(false); diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 9c4adf953f3..a6e3d3361dc 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -232,9 +232,8 @@ export class ReplVariablesRenderer extends AbstractExpressionsRenderer { private readonly linkDetector: LinkDetector, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, - @IThemeService themeService: IThemeService, ) { - super(debugService, contextViewService, themeService); + super(debugService, contextViewService); } protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 15ee525c464..be1f0d135ad 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -402,9 +402,8 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, - @IThemeService themeService: IThemeService, ) { - super(debugService, contextViewService, themeService); + super(debugService, contextViewService); } get templateId(): string { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 702eadc54e1..352cf44e602 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -272,9 +272,8 @@ class WatchExpressionsRenderer extends AbstractExpressionsRenderer { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, - @IThemeService themeService: IThemeService, ) { - super(debugService, contextViewService, themeService); + super(debugService, contextViewService); } get templateId() { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index e146eafbd1c..55c46a9095f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -69,7 +69,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import * as semver from 'vs/base/common/semver/semver'; -import { getKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; class NavBar extends Disposable { @@ -1556,7 +1556,7 @@ export class ExtensionEditor extends EditorPane { const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => { const element = $(''); - const kbl = new KeybindingLabel(element, OS, getKeybindingLabelStyles()); + const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles); kbl.set(keybinding); return element; }; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9cc0159b382..8e3d0624a18 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -23,7 +23,6 @@ import { IFilesConfiguration, UndoConfirmLevel } from 'vs/workbench/contrib/file import { dirname, joinPath, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { once } from 'vs/base/common/functional'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { equals, deepClone } from 'vs/base/common/objects'; @@ -61,6 +60,7 @@ import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAcce import { IgnoreFile } from 'vs/workbench/services/search/common/ignoreFile'; import { ResourceSet } from 'vs/base/common/map'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -467,9 +467,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer { done(inputBox.isInputValid(), true); }), - label, - styler + label ]; return toDisposable(() => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 604ddedb213..d362b430c44 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -21,8 +21,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { attachToggleStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; import { InputState, ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; @@ -38,7 +37,6 @@ export class InputCodeEditorView extends CodeEditorView { viewModel: IObservable, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, - @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, ) { super(instantiationService, viewModel, configurationService); @@ -53,7 +51,7 @@ export class InputCodeEditorView extends CodeEditorView { getIntersectingGutterItems: (range, reader) => { return this.modifiedBaseRangeGutterItemInfos.read(reader); }, - createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService), + createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService), }) ); } @@ -375,7 +373,6 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt item: ModifiedBaseRangeGutterItemModel, target: HTMLElement, contextMenuService: IContextMenuService, - themeService: IThemeService ) { super(); @@ -384,12 +381,11 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt const checkBox = new Toggle({ isChecked: false, title: '', - icon: Codicon.check + icon: Codicon.check, + ...defaultToggleStyles }); checkBox.domNode.classList.add('accept-conflict-group'); - this._register(attachToggleStyler(checkBox, themeService)); - this._register( addDisposableListener(checkBox.domNode, EventType.MOUSE_DOWN, (e) => { const item = this.item.get(); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 59d525ee6a0..e7455c46dce 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { FindInput, IFindInputOptions, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; -import { IReplaceInputStyles, ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; +import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; +import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Widget } from 'vs/base/browser/ui/widget'; @@ -18,9 +18,8 @@ import * as nls from 'vs/nls'; import { ContextScopedReplaceInput, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; -import { IColorTheme, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { parseReplaceString, ReplacePattern } from 'vs/editor/contrib/find/browser/replacePattern'; import { Codicon } from 'vs/base/common/codicons'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -36,7 +35,8 @@ import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contr import { isSafari } from 'vs/base/common/platform'; import { ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultInputBoxStyles, defaultProgressBarStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -154,6 +154,7 @@ class NotebookFindInput extends FindInput { private _actionbar: ActionBar | null = null; private _filterChecked: boolean = false; private _filtersAction: IAction; + private _toggleStyles: IToggleStyles; constructor( readonly filters: NotebookFindFilters, @@ -166,6 +167,8 @@ class NotebookFindInput extends FindInput { ) { super(parent, contextViewProvider, options); + this._toggleStyles = options.toggleStyles; + this._register(registerAndCreateHistoryNavigationContext(contextKeyService, this.inputBox)); this._filtersAction = new Action('notebookFindFilterAction', NOTEBOOK_FIND_FILTERS, 'notebook-filters ' + ThemeIcon.asClassName(filterIcon)); this._filtersAction.checked = false; @@ -225,12 +228,12 @@ class NotebookFindInput extends FindInput { this.applyStyles(); } - protected override applyStyles(): void { - super.applyStyles(); + private applyStyles(): void { + const toggleStyles = this._toggleStyles; - this._filterButtonContainer.style.borderColor = this._filterChecked && this.inputActiveOptionBorder ? this.inputActiveOptionBorder.toString() : ''; - this._filterButtonContainer.style.color = this._filterChecked && this.inputActiveOptionForeground ? this.inputActiveOptionForeground.toString() : 'inherit'; - this._filterButtonContainer.style.backgroundColor = this._filterChecked && this.inputActiveOptionBackground ? this.inputActiveOptionBackground.toString() : ''; + this._filterButtonContainer.style.borderColor = (this._filterChecked && toggleStyles.inputActiveOptionBorder) || ''; + this._filterButtonContainer.style.color = (this._filterChecked && toggleStyles.inputActiveOptionForeground) || 'inherit'; + this._filterButtonContainer.style.backgroundColor = (this._filterChecked && toggleStyles.inputActiveOptionBackground) || ''; } getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { @@ -299,7 +302,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._scopedContextKeyService = contextKeyService.createScoped(this._domNode); const progressContainer = dom.$('.find-replace-progress'); - this._progressBar = new ProgressBar(progressContainer, getProgressBarStyles()); + this._progressBar = new ProgressBar(progressContainer, defaultProgressBarStyles); this._domNode.appendChild(progressContainer); const isInteractiveWindow = contextKeyService.getContextKeyValue('notebookType') === 'interactive'; @@ -351,7 +354,9 @@ export abstract class SimpleFindReplaceWidget extends Widget { } }, flexibleWidth: true, - showCommonFindToggles: true + showCommonFindToggles: true, + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles } )); @@ -452,7 +457,9 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, { label: NLS_REPLACE_INPUT_LABEL, placeholder: NLS_REPLACE_INPUT_PLACEHOLDER, - history: [] + history: [], + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles }, contextKeyService, false)); this._innerReplaceDomNode.appendChild(this._replaceInput.domNode); this._replaceInputFocusTracker = this._register(dom.trackFocus(this._replaceInput.domNode)); @@ -581,45 +588,6 @@ export abstract class SimpleFindReplaceWidget extends Widget { return this._focusTracker; } - public updateTheme(theme: IColorTheme): void { - const inputStyles: IFindInputStyles = { - inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), - inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground), - inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), - inputBackground: theme.getColor(inputBackground), - inputForeground: theme.getColor(inputForeground), - inputBorder: theme.getColor(inputBorder), - inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), - inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), - inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), - inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), - inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), - inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), - inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), - }; - this._findInput.style(inputStyles); - const replaceStyles: IReplaceInputStyles = { - inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), - inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground), - inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), - inputBackground: theme.getColor(inputBackground), - inputForeground: theme.getColor(inputForeground), - inputBorder: theme.getColor(inputBorder), - inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), - inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), - inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), - inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), - inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), - inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), - inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), - }; - this._replaceInput.style(replaceStyles); - } - private _onStateChanged(e: FindReplaceStateChangedEvent): void { this._updateButtons(); this._updateMatchesCount(); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index d422ae6e7eb..3589f801003 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -21,7 +21,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget'; @@ -81,7 +80,6 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi _notebookEditor: INotebookEditor, @IContextViewService contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, - @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IMenuService menuService: IMenuService, @@ -93,10 +91,6 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi DOM.append(this._notebookEditor.getDomNode(), this.getDomNode()); this._findWidgetFocused = KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED.bindTo(contextKeyService); this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e))); - this.updateTheme(themeService.getColorTheme()); - this._register(themeService.onDidColorThemeChange(() => { - this.updateTheme(themeService.getColorTheme()); - })); this._register(this._state.onFindReplaceStateChange((e) => { this.onInputChanged(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts index df45ee5073b..6b4ac184e7b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; @@ -21,10 +21,10 @@ export class CellProgressBar extends CellContentPart { @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService) { super(); - this._progressBar = this._register(new ProgressBar(editorContainer, getProgressBarStyles())); + this._progressBar = this._register(new ProgressBar(editorContainer, defaultProgressBarStyles)); this._progressBar.hide(); - this._collapsedProgressBar = this._register(new ProgressBar(collapsedInputContainer, getProgressBarStyles())); + this._collapsedProgressBar = this._register(new ProgressBar(collapsedInputContainer, defaultProgressBarStyles)); this._collapsedProgressBar.hide(); } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 09e74bcd91d..644cc8bb85a 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -35,7 +35,7 @@ import { ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { AbstractTreeViewState, IAbstractTreeViewState, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree'; import { URI } from 'vs/base/common/uri'; import { ctxAllCollapsed, ctxFilterOnType, ctxFollowsCursor, ctxSortMode, IOutlinePane, OutlineSortOrder } from 'vs/workbench/contrib/outline/browser/outline'; -import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; class OutlineTreeSorter implements ITreeSorter { @@ -136,7 +136,7 @@ export class OutlinePane extends ViewPane implements IOutlinePane { const progressContainer = dom.$('.outline-progress'); this._message = dom.$('.outline-message'); - this._progressBar = new ProgressBar(progressContainer, getProgressBarStyles()); + this._progressBar = new ProgressBar(progressContainer, defaultProgressBarStyles); this._treeContainer = dom.$('.outline-tree'); dom.append(container, progressContainer, this._message, this._treeContainer); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 27abf3b0cbf..3f0c333f2eb 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -20,7 +20,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; -import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorWidgetBackground, editorWidgetForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -28,7 +28,7 @@ import { SearchWidget, SearchOptions } from 'vs/workbench/contrib/preferences/br import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises, timeout } from 'vs/base/common/async'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { getKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultInputBoxStyles, defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface KeybindingsSearchOptions extends SearchOptions { recordEnter?: boolean; @@ -63,7 +63,6 @@ export class KeybindingsSearchWidget extends SearchWidget { @IKeybindingService keybindingService: IKeybindingService, ) { super(parent, options, contextViewService, instantiationService, themeService, contextKeyService, keybindingService); - this._register(attachInputBoxStyler(this.inputBox, themeService)); this._register(toDisposable(() => this.stopRecordingKeys())); this._firstPart = null; this._chordPart = null; @@ -204,7 +203,7 @@ export class DefineKeybindingWidget extends Widget { } })); - this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message, history: [] })); + this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message, history: [], inputBoxStyles: defaultInputBoxStyles })); this._keybindingInputWidget.startRecordingKeys(); this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); this._register(this._keybindingInputWidget.onEnter(() => this.hide())); @@ -277,12 +276,12 @@ export class DefineKeybindingWidget extends Widget { dom.clearNode(this._outputNode); dom.clearNode(this._showExistingKeybindingsNode); - const firstLabel = new KeybindingLabel(this._outputNode, OS, getKeybindingLabelStyles()); + const firstLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); firstLabel.set(withNullAsUndefined(this._firstPart)); if (this._chordPart) { this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to"))); - const chordLabel = new KeybindingLabel(this._outputNode, OS, getKeybindingLabelStyles()); + const chordLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); chordLabel.set(this._chordPart); } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index a3a1187e91d..0e71587ff2a 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -35,36 +35,23 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { WorkbenchTable } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { attachStylerCallback, attachInputBoxStyler, attachToggleStyler } from 'vs/platform/theme/common/styler'; +import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { Emitter, Event } from 'vs/base/common/event'; import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; -import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IKeybindingItemEntry, IKeybindingsEditorPane } from 'vs/workbench/services/preferences/common/preferences'; import { keybindingsRecordKeysIcon, keybindingsSortIcon, keybindingsAddIcon, preferencesClearInputIcon, keybindingsEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { getKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultInputBoxStyles, defaultKeybindingLabelStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const $ = DOM.$; -class ThemableToggleActionViewItem extends ToggleActionViewItem { - - constructor(context: any, action: IAction, options: IActionViewItemOptions, private readonly themeService: IThemeService) { - super(context, action, options); - } - - override render(container: HTMLElement): void { - super.render(container); - this._register(attachToggleStyler(this.toggle, this.themeService)); - } -} - export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorPane { static readonly ID: string = 'workbench.editor.keybindings'; @@ -325,6 +312,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP recordEnter: true, quoteRecordedKeys: true, history: this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] || [], + inputBoxStyles: defaultInputBoxStyles })); this._register(this.searchWidget.onDidChange(searchValue => { clearInputAction.enabled = !!searchValue; @@ -365,7 +353,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP const toolBar = this._register(new ToolBar(this.actionsContainer, this.contextMenuService, { actionViewItemProvider: (action: IAction) => { if (action.id === this.sortByPrecedenceAction.id || action.id === this.recordKeysAction.id) { - return new ThemableToggleActionViewItem(null, action, { keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel() }, this.themeService); + return new ToggleActionViewItem(null, action, { keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles }); } return undefined; }, @@ -939,7 +927,7 @@ class KeybindingColumnRenderer implements ITableRenderer = disposables.add(new Emitter()); const onDidAccept: Event = _onDidAccept.event; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index cab5ff5e1f1..47a1eb06a31 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -29,7 +29,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; @@ -452,8 +452,6 @@ export class SearchWidget extends Widget { protected createInputBox(parent: HTMLElement): HistoryInputBox { const showHistoryHint = () => showHistoryKeybindingHint(this.keybindingService); const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, { ...this.options, showHistoryHint }, this.contextKeyService)); - this._register(attachInputBoxStyler(box, this.themeService)); - return box; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index aca340635d4..a84832bc24e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -8,7 +8,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -36,7 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; @@ -64,7 +64,7 @@ import { getIndicatorsLabelAriaLabel, ISettingOverrideClickEvent, SettingsTreeIn import { ILanguageService } from 'vs/editor/common/languages/language'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { defaultButtonStyles, getButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, getButtonStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; const $ = DOM.$; @@ -1493,6 +1493,12 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I } } +const settingsInputBoxStyles = getInputBoxStyle({ + inputBackground: settingsTextInputBackground, + inputForeground: settingsTextInputForeground, + inputBorder: settingsTextInputBorder +}); + abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer { private readonly MULTILINE_MAX_HEIGHT = 150; @@ -1503,15 +1509,11 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple const inputBoxOptions: IInputOptions = { flexibleHeight: useMultiline, flexibleWidth: false, - flexibleMaxHeight: this.MULTILINE_MAX_HEIGHT + flexibleMaxHeight: this.MULTILINE_MAX_HEIGHT, + inputBoxStyles: settingsInputBoxStyles }; const inputBox = new InputBox(common.controlElement, this._contextViewService, inputBoxOptions); common.toDispose.add(inputBox); - common.toDispose.add(attachInputBoxStyler(inputBox, this._themeService, { - inputBackground: settingsTextInputBackground, - inputForeground: settingsTextInputForeground, - inputBorder: settingsTextInputBorder - })); common.toDispose.add( inputBox.onDidChange(e => { template.onChange?.(e); @@ -1708,6 +1710,12 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre } } +const settingsNumberInputBoxStyles = getInputBoxStyle({ + inputBackground: settingsNumberInputBackground, + inputForeground: settingsNumberInputForeground, + inputBorder: settingsNumberInputBorder +}); + export class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_NUMBER_TEMPLATE_ID; @@ -1715,13 +1723,8 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT const common = super.renderCommonTemplate(null, _container, 'number'); const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message')); - const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number' }); + const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number', inputBoxStyles: settingsNumberInputBoxStyles }); common.toDispose.add(inputBox); - common.toDispose.add(attachInputBoxStyler(inputBox, this._themeService, { - inputBackground: settingsNumberInputBackground, - inputForeground: settingsNumberInputForeground, - inputBorder: settingsNumberInputBorder - })); common.toDispose.add( inputBox.onDidChange(e => { template.onChange?.(e); @@ -1790,7 +1793,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = new DisposableStore(); - const checkbox = new Toggle({ icon: Codicon.check, actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: undefined }); + const checkbox = new Toggle({ icon: Codicon.check, actionClassName: 'setting-value-checkbox', isChecked: true, title: '', ...unthemedToggleStyles }); controlElement.appendChild(checkbox.domNode); toDispose.add(checkbox); toDispose.add(checkbox.onChange(() => { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 08371f9de22..549ccf18d60 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -8,7 +8,7 @@ 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 { Button } from 'vs/base/browser/ui/button/button'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { IAction } from 'vs/base/common/actions'; @@ -22,11 +22,11 @@ import { isDefined, isUndefinedOrNull } from 'vs/base/common/types'; import 'vs/css!./media/settingsWidgets'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { settingsDiscardIcon, settingsEditIcon, settingsRemoveIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; const $ = DOM.$; @@ -614,15 +614,15 @@ export class ListSettingWidget extends AbstractListSettingWidget let siblingInput: InputBox | undefined; if (!isUndefinedOrNull(item.sibling)) { siblingInput = new InputBox(rowElement, this.contextViewService, { - placeholder: this.getLocalizedStrings().siblingInputPlaceholder + placeholder: this.getLocalizedStrings().siblingInputPlaceholder, + inputBoxStyles: getInputBoxStyle({ + inputBackground: settingsTextInputBackground, + inputForeground: settingsTextInputForeground, + inputBorder: settingsTextInputBorder + }) }); siblingInput.element.classList.add('setting-list-siblingInput'); this.listDisposables.add(siblingInput); - this.listDisposables.add(attachInputBoxStyler(siblingInput, this.themeService, { - inputBackground: settingsTextInputBackground, - inputForeground: settingsTextInputForeground, - inputBorder: settingsTextInputBorder - })); siblingInput.value = item.sibling; this.listDisposables.add( @@ -688,15 +688,15 @@ export class ListSettingWidget extends AbstractListSettingWidget private renderInputBox(value: ObjectValue, rowElement: HTMLElement): InputBox { const valueInput = new InputBox(rowElement, this.contextViewService, { - placeholder: this.getLocalizedStrings().inputPlaceholder + placeholder: this.getLocalizedStrings().inputPlaceholder, + inputBoxStyles: getInputBoxStyle({ + inputBackground: settingsTextInputBackground, + inputForeground: settingsTextInputForeground, + inputBorder: settingsTextInputBorder + }) }); valueInput.element.classList.add('setting-list-valueInput'); - this.listDisposables.add(attachInputBoxStyler(valueInput, this.themeService, { - inputBackground: settingsTextInputBackground, - inputForeground: settingsTextInputForeground, - inputBorder: settingsTextInputBorder - })); this.listDisposables.add(valueInput); valueInput.value = value.data.toString(); @@ -1026,15 +1026,15 @@ export class ObjectSettingDropdownWidget extends AbstractListSettingWidget('forwardedPortsViewEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled.")); export const openPreviewEnabledContext = new RawContextKey('openPreviewEnabled', false); @@ -347,7 +346,6 @@ class ActionBarRenderer extends Disposable implements ITableRenderer { return done(inputBox.validate() !== MessageType.ERROR, true); - }), - styler + }) ]; return toDisposable(() => { @@ -840,7 +836,7 @@ export class TunnelPanel extends ViewPane { widgetContainer.classList.add('file-icon-themable-tree', 'show-file-icons'); const actionBarRenderer = new ActionBarRenderer(this.instantiationService, this.contextKeyService, - this.menuService, this.contextViewService, this.themeService, this.remoteExplorerService, this.commandService, + this.menuService, this.contextViewService, this.remoteExplorerService, this.commandService, this.configurationService, this.hoverService); const columns = [new IconColumn(), new PortColumn(), new LocalAddressColumn(), new RunningProcessColumn()]; if (this.tunnelService.canChangePrivacy) { diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 30e8ca1e9ea..5d9e6fb31e2 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -12,15 +12,13 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event as CommonEvent } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import type { IThemable } from 'vs/base/common/styler'; import * as nls from 'vs/nls'; import { ContextScopedHistoryInputBox } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { attachToggleStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface IOptions { placeholder?: string; @@ -29,9 +27,10 @@ export interface IOptions { width?: number; ariaLabel?: string; history?: string[]; + inputBoxStyles: IInputBoxStyles; } -export class PatternInputWidget extends Widget implements IThemable { +export class PatternInputWidget extends Widget { static OPTION_CHANGE: string = 'optionChange'; @@ -48,8 +47,7 @@ export class PatternInputWidget extends Widget implements IThemable { private _onCancel = this._register(new Emitter()); onCancel: CommonEvent = this._onCancel.event; - constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), - @IThemeService protected themeService: IThemeService, + constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService protected readonly configurationService: IConfigurationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -135,10 +133,6 @@ export class PatternInputWidget extends Widget implements IThemable { this.inputBox.showPreviousValue(); } - style(styles: IInputBoxStyles): void { - this.inputBox.style(styles); - } - private render(options: IOptions): void { this.domNode = document.createElement('div'); this.domNode.style.width = this.width + 'px'; @@ -153,9 +147,9 @@ export class PatternInputWidget extends Widget implements IThemable { validation: undefined }, history: options.history || [], - showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService) + showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), + inputBoxStyles: options.inputBoxStyles }, this.contextKeyService); - this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this._register(this.inputBox.onDidChange(() => this._onSubmit.fire(true))); this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); @@ -190,13 +184,12 @@ export class IncludePatternInputWidget extends PatternInputWidget { private _onChangeSearchInEditorsBoxEmitter = this._register(new Emitter()); onChangeSearchInEditorsBox = this._onChangeSearchInEditorsBoxEmitter.event; - constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), - @IThemeService themeService: IThemeService, + constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, ) { - super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService, keybindingService); + super(parent, contextViewProvider, options, contextKeyService, configurationService, keybindingService); } private useSearchInEditorsBox!: Toggle; @@ -224,6 +217,7 @@ export class IncludePatternInputWidget extends PatternInputWidget { icon: Codicon.book, title: nls.localize('onlySearchInOpenEditors', "Search only in Open Editors"), isChecked: false, + ...defaultToggleStyles })); this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => { this._onChangeSearchInEditorsBoxEmitter.fire(); @@ -231,7 +225,6 @@ export class IncludePatternInputWidget extends PatternInputWidget { this.inputBox.focus(); } })); - this._register(attachToggleStyler(this.useSearchInEditorsBox, this.themeService)); controlsDiv.appendChild(this.useSearchInEditorsBox.domNode); super.renderSubcontrols(controlsDiv); } @@ -242,13 +235,12 @@ export class ExcludePatternInputWidget extends PatternInputWidget { private _onChangeIgnoreBoxEmitter = this._register(new Emitter()); onChangeIgnoreBox = this._onChangeIgnoreBoxEmitter.event; - constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), - @IThemeService themeService: IThemeService, + constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, ) { - super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService, keybindingService); + super(parent, contextViewProvider, options, contextKeyService, configurationService, keybindingService); } private useExcludesAndIgnoreFilesBox!: Toggle; @@ -277,6 +269,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { actionClassName: 'useExcludesAndIgnoreFiles', title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"), isChecked: true, + ...defaultToggleStyles })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { this._onChangeIgnoreBoxEmitter.fire(); @@ -284,7 +277,6 @@ export class ExcludePatternInputWidget extends PatternInputWidget { this.inputBox.focus(); } })); - this._register(attachToggleStyler(this.useExcludesAndIgnoreFilesBox, this.themeService)); controlsDiv.appendChild(this.useExcludesAndIgnoreFilesBox.domNode); super.renderSubcontrols(controlsDiv); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 48b8b31194e..9f5a9f44590 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -48,6 +48,7 @@ import { IOpenerService, withSelection } from 'vs/platform/opener/common/opener' import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, foreground, listActiveSelectionForeground, textLinkActiveForeground, textLinkForeground, toolbarActiveBackground, toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -67,7 +68,7 @@ import { IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActi import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { renderSearchMessage } from 'vs/workbench/contrib/search/browser/searchMessage'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate } from 'vs/workbench/contrib/search/browser/searchResultsView'; -import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; +import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; @@ -350,6 +351,7 @@ export class SearchView extends ViewPane { placeholder: nls.localize('placeholder.includes', "e.g. *.ts, src/**/include"), showPlaceholderOnFocus: true, history: patternIncludesHistory, + inputBoxStyles: defaultInputBoxStyles })); this.inputPatternIncludes.setValue(patternIncludes); @@ -369,6 +371,7 @@ export class SearchView extends ViewPane { placeholder: nls.localize('placeholder.excludes', "e.g. *.ts, src/**/exclude"), showPlaceholderOnFocus: true, history: patternExclusionsHistory, + inputBoxStyles: defaultInputBoxStyles })); this.inputPatternExcludes.setValue(patternExclusions); @@ -446,7 +449,7 @@ export class SearchView extends ViewPane { const showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true; const preserveCase = this.viewletState['query.preserveCase'] === true; - this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, { + this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, { value: contentPattern, replaceValue: replaceText, isRegex: isRegex, @@ -454,7 +457,9 @@ export class SearchView extends ViewPane { isWholeWords: isWholeWords, searchHistory: searchHistory, replaceHistory: replaceHistory, - preserveCase: preserveCase + preserveCase: preserveCase, + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles })); if (showReplace) { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 578c1d2f74a..46d48bb7f75 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -9,7 +9,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonOptions } from 'vs/base/browser/ui/button/button'; import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; -import { IMessage, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IInputBoxStyles, IMessage, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; @@ -24,18 +24,18 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { attachFindReplaceInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { IViewsService } from 'vs/workbench/common/views'; import { searchReplaceAllIcon, searchHideReplaceIcon, searchShowContextIcon, searchShowReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { ToggleSearchEditorContextLinesCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; /** Specified in searchview.css */ export const SingleLineInputHeight = 24; @@ -51,6 +51,8 @@ export interface ISearchWidgetOptions { preserveCase?: boolean; _hideReplaceToggle?: boolean; // TODO: Search Editor's replace experience showContextToggle?: boolean; + inputBoxStyles: IInputBoxStyles; + toggleStyles: IToggleStyles; } class ReplaceAllAction extends Action { @@ -155,7 +157,6 @@ export class SearchWidget extends Widget { container: HTMLElement, options: ISearchWidgetOptions, @IContextViewService private readonly contextViewService: IContextViewService, - @IThemeService private readonly themeService: IThemeService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IClipboardService private readonly clipboardServce: IClipboardService, @@ -319,12 +320,13 @@ export class SearchWidget extends Widget { showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), flexibleHeight: true, flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT, - showCommonFindToggles: true + showCommonFindToggles: true, + inputBoxStyles: options.inputBoxStyles, + toggleStyles: options.toggleStyles }; const searchInputContainer = dom.append(parent, dom.$('.search-container.input-box')); this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService)); - this._register(attachFindReplaceInputBoxStyler(this.searchInput, this.themeService)); this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent)); this.searchInput.setValue(options.value || ''); this.searchInput.setRegex(!!options.isRegex); @@ -363,12 +365,13 @@ export class SearchWidget extends Widget { this.showContextToggle = new Toggle({ isChecked: false, title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId), this.keybindingService), - icon: searchShowContextIcon + icon: searchShowContextIcon, + ...defaultToggleStyles }); this._register(this.showContextToggle.onChange(() => this.onContextLinesChanged())); if (options.showContextToggle) { - this.contextLinesInput = new InputBox(searchInputContainer, this.contextViewService, { type: 'number' }); + this.contextLinesInput = new InputBox(searchInputContainer, this.contextViewService, { type: 'number', inputBoxStyles: defaultInputBoxStyles }); this.contextLinesInput.element.classList.add('context-lines-input'); this.contextLinesInput.value = '' + (this.configurationService.getValue('search').searchEditor.defaultNumberOfContextLines ?? 1); this._register(this.contextLinesInput.onDidChange((value: string) => { @@ -377,7 +380,6 @@ export class SearchWidget extends Widget { } this.onContextLinesChanged(); })); - this._register(attachInputBoxStyler(this.contextLinesInput, this.themeService)); dom.append(searchInputContainer, this.showContextToggle.domNode); } } @@ -413,7 +415,9 @@ export class SearchWidget extends Widget { history: options.replaceHistory, showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), flexibleHeight: true, - flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT + flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT, + inputBoxStyles: options.inputBoxStyles, + toggleStyles: options.toggleStyles }, this.contextKeyService, true)); this._register(this.replaceInput.onDidOptionChange(viaKeyboard => { @@ -422,7 +426,6 @@ export class SearchWidget extends Widget { } })); - this._register(attachFindReplaceInputBoxStyler(this.replaceInput, this.themeService)); this.replaceInput.onKeyDown((keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); this.replaceInput.setValue(options.replaceValue || ''); this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire())); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 9dbee9c59fb..cdea9d6e3c8 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -33,7 +33,6 @@ import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progre import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { inputBorder, registerColor, searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { AbstractTextCodeEditor } from 'vs/workbench/browser/parts/editor/textCodeEditor'; @@ -61,6 +60,7 @@ import { renderSearchMessage } from 'vs/workbench/contrib/search/browser/searchM import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { UnusualLineTerminatorsDetector } from 'vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators'; import { isHighContrast } from 'vs/platform/theme/common/theme'; +import { defaultToggleStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -143,7 +143,9 @@ export class SearchEditor extends AbstractTextCodeEditor private createQueryEditor(container: HTMLElement, scopedInstantiationService: IInstantiationService, inputBoxFocusedContextKey: IContextKey) { - this.queryEditorWidget = this._register(scopedInstantiationService.createInstance(SearchWidget, container, { _hideReplaceToggle: true, showContextToggle: true })); + const searchEditorInputboxStyles = getInputBoxStyle({ inputBorder: searchEditorTextInputBorder }); + + this.queryEditorWidget = this._register(scopedInstantiationService.createInstance(SearchWidget, container, { _hideReplaceToggle: true, showContextToggle: true, inputBoxStyles: searchEditorInputboxStyles, toggleStyles: defaultToggleStyles })); this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout())); this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout())); this._register(this.queryEditorWidget.onSearchSubmit(({ delay }) => this.triggerSearch({ delay }))); @@ -185,6 +187,7 @@ export class SearchEditor extends AbstractTextCodeEditor DOM.append(folderIncludesList, DOM.$('h4', undefined, filesToIncludeTitle)); this.inputPatternIncludes = this._register(scopedInstantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: localize('label.includes', 'Search Include Patterns'), + inputBoxStyles: searchEditorInputboxStyles })); this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); this._register(this.inputPatternIncludes.onChangeSearchInEditorsBox(() => this.triggerSearch())); @@ -195,13 +198,11 @@ export class SearchEditor extends AbstractTextCodeEditor DOM.append(excludesList, DOM.$('h4', undefined, excludesTitle)); this.inputPatternExcludes = this._register(scopedInstantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, { ariaLabel: localize('label.excludes', 'Search Exclude Patterns'), + inputBoxStyles: searchEditorInputboxStyles })); this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); this._register(this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch())); - [this.queryEditorWidget.searchInput, this.inputPatternIncludes, this.inputPatternExcludes, this.queryEditorWidget.contextLinesInput].map(input => - this._register(attachInputBoxStyler(input, this.themeService, { inputBorder: searchEditorTextInputBorder }))); - // Messages this.messageBox = DOM.append(container, DOM.$('.messages.text-search-provider-messages')); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts index 81f970de0c4..025b2f28856 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts @@ -9,7 +9,7 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; @@ -37,9 +37,7 @@ export class TerminalFindWidget extends SimpleFindWidget { this._findInputFocused = TerminalContextKeys.findInputFocus.bindTo(this._contextKeyService); this._findWidgetFocused = TerminalContextKeys.findFocus.bindTo(this._contextKeyService); this._findWidgetVisible = TerminalContextKeys.findVisible.bindTo(this._contextKeyService); - this.updateTheme(this._themeService.getColorTheme()); - this._register(this._themeService.onDidColorThemeChange((theme?: IColorTheme) => { - this.updateTheme(theme ?? this._themeService.getColorTheme()); + this._register(this._themeService.onDidColorThemeChange(() => { if (this.isVisible()) { this.find(true, true); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts b/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts index e56f75730b1..57355534575 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts @@ -13,8 +13,8 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; -import { inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { asCssValue, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { commandHistoryFuzzySearchIcon, commandHistoryOutputIcon, commandHistoryRemoveIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { getCommandHistory, getDirectoryHistory, getShellFileHistory } from 'vs/workbench/contrib/terminal/common/history'; @@ -43,7 +43,6 @@ export async function showRunRecentQuickPick( const instantiationService = accessor.get(IInstantiationService); const quickInputService = accessor.get(IQuickInputService); const storageService = accessor.get(IStorageService); - const themeService = accessor.get(IThemeService); const runRecentStorageKey = `${TerminalStorageKeys.PinnedRecentCommandsPrefix}.${instance.shellType}`; let placeholder: string; @@ -208,9 +207,9 @@ export async function showRunRecentQuickPick( title: 'Fuzzy search', icon: commandHistoryFuzzySearchIcon, isChecked: filterMode === 'fuzzy', - inputActiveOptionBorder: themeService.getColorTheme().getColor(inputActiveOptionBorder), - inputActiveOptionForeground: themeService.getColorTheme().getColor(inputActiveOptionForeground), - inputActiveOptionBackground: themeService.getColorTheme().getColor(inputActiveOptionBackground) + inputActiveOptionBorder: asCssValue(inputActiveOptionBorder), + inputActiveOptionForeground: asCssValue(inputActiveOptionForeground), + inputActiveOptionBackground: asCssValue(inputActiveOptionBackground) }); fuzzySearchToggle.onChange(() => { instantiationService.invokeFunction(showRunRecentQuickPick, instance, terminalInRunCommandPicker, type, fuzzySearchToggle.checked ? 'fuzzy' : 'contiguous', quickPick.value); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 82d9b1747e2..a47005a3d7f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -37,7 +37,6 @@ import { IEditableData } from 'vs/workbench/common/views'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { once } from 'vs/base/common/functional'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; @@ -47,6 +46,7 @@ import { IProcessDetails } from 'vs/platform/terminal/common/terminalProcess'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getTerminalResourcesFromDragEvent, parseTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { getShellIntegrationTooltip } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const $ = DOM.$; @@ -394,9 +394,9 @@ class TerminalTabsRenderer implements IListRenderer { done(inputBox.isInputValid(), true); - }), - styler + }) ]; return toDisposable(() => { diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index c2bc7aa63ef..30c71634a23 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -31,7 +31,7 @@ import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser import { DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { getKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; const $ = dom.$; @@ -161,7 +161,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr const dt = dom.append(dl, $('dt')); dt.textContent = entry.text; const dd = dom.append(dl, $('dd')); - const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...getKeybindingLabelStyles() }); + const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); keybinding.set(this.keybindingService.lookupKeybinding(entry.id)); }); }; diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 5f8f9e3ba2d..8634a43a6e3 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -350,7 +350,6 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD if (initInfo.options.enableFindWidget) { this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); - this.styledFindWidget(); } this._encodedWebviewOriginPromise.then(encodedWebviewOrigin => { @@ -668,13 +667,8 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD const screenReader = this._accessibilityService.isScreenReaderOptimized(); this._send('styles', { styles, activeTheme, themeId, themeLabel, reduceMotion, screenReader }); - - this.styledFindWidget(); } - private styledFindWidget() { - this._webviewFindWidget?.updateTheme(this.webviewThemeDataProvider.getTheme()); - } protected handleFocusChange(isFocused: boolean): void { this._focused = isFocused; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 001d34a2fad..e6e015a1bce 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -68,7 +68,7 @@ import { restoreWalkthroughsConfigurationKey, RestoreWalkthroughsConfigurationVa import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -730,6 +730,7 @@ export class GettingStartedPage extends EditorPane { actionClassName: 'getting-started-checkbox', isChecked: this.configurationService.getValue(configurationKey) === 'welcomePage', title: localize('checkboxTitle', "When checked, this page will be shown on startup."), + ...defaultToggleStyles }); showOnStartupCheckbox.domNode.id = 'showOnStartup'; const showOnStartupLabel = $('label.caption', { for: 'showOnStartup' }, localize('welcomePage.showOnStartup', "Show welcome page on startup")); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 6e17fd9d353..99d4f988f14 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -35,7 +35,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { buttonBackground, buttonSecondaryBackground, editorErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceContextService, toWorkspaceIdentifier, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; @@ -55,7 +55,7 @@ import { hasDriveLetter, toSlashes } from 'vs/base/common/extpath'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IProductService } from 'vs/platform/product/common/productService'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export const shieldIcon = registerIcon('workspace-trust-banner', Codicon.shield, localize('shieldIcon', 'Icon for workspace trust ion the banner.')); @@ -473,8 +473,7 @@ class TrustedUriPathColumnRenderer implements ITableRenderer this.table.validateUri(value, this.currentItem) - } + }, + inputBoxStyles: defaultInputBoxStyles }); const disposables = new DisposableStore(); - disposables.add(attachInputBoxStyler(pathInput, this.themeService)); - const renderDisposables = disposables.add(new DisposableStore()); return { diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index 60eb43ec230..8a2558d6877 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -12,7 +12,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; @@ -34,7 +33,6 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @IDialogService private dialogService: IDialogService, @ILogService logService: ILogService, @ILayoutService layoutService: ILayoutService, - @IThemeService themeService: IThemeService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IProductService productService: IProductService, @@ -43,7 +41,7 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC ) { super(); - this.browserImpl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, instantiationService, productService, clipboardService); + this.browserImpl = new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService); this.nativeImpl = new NativeDialogHandler(logService, nativeHostService, productService, clipboardService); this.model = (this.dialogService as DialogService).model; diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 2e423fc7c1a..beb4c52bde1 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -17,8 +17,6 @@ import { Event, Emitter } from 'vs/base/common/event'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; -import { attachDialogStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; @@ -26,7 +24,7 @@ import { parseLinkedText } from 'vs/base/common/linkedText'; import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { stripIcons } from 'vs/base/common/iconLabels'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export class ProgressService extends Disposable implements IProgressService { @@ -40,7 +38,6 @@ export class ProgressService extends Disposable implements IProgressService { @INotificationService private readonly notificationService: INotificationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @ILayoutService private readonly layoutService: ILayoutService, - @IThemeService private readonly themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(); @@ -565,12 +562,14 @@ export class ProgressService extends Disposable implements IProgressService { } } }, - buttonStyles: defaultButtonStyles + buttonStyles: defaultButtonStyles, + checkboxStyles: defaultCheckboxStyles, + inputBoxStyles: defaultInputBoxStyles, + dialogStyles: defaultDialogStyles } ); disposables.add(dialog); - disposables.add(attachDialogStyler(dialog, this.themeService)); dialog.show().then(dialogResult => { onDidCancel?.(dialogResult.button); From c20c7d5adb8362822b135c5bb5880fe1580aaa73 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 17 Nov 2022 10:21:29 -0500 Subject: [PATCH 43/67] Update getting-started assignment (#166592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove i18n causing classifier to fail due to missing label * 🎁 Add `killOnServerStop` to debug configuration (#163779) * 🎁 Add `killOnServerStop` to schema Signed-off-by: Babak K. Shandiz * 📜 Add description for `killOnServerStop` Signed-off-by: Babak K. Shandiz * 🔨 Stop created debug session on server stop Signed-off-by: Babak K. Shandiz * 🔨 Push kill listeners into another disposable container Signed-off-by: Babak K. Shandiz * 🐛 Prevent leak when new debug session fails to start Signed-off-by: Babak K. Shandiz * 🔨 Use more verbose name for debug session tracker ID Signed-off-by: Babak K. Shandiz Signed-off-by: Babak K. Shandiz * assign bhayva to getting started Signed-off-by: Babak K. Shandiz Co-authored-by: Babak K. Shandiz --- .github/classifier.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/classifier.json b/.github/classifier.json index 1f162f2e03f..dad8fe6cb76 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -78,6 +78,7 @@ "file-watcher": {"assign": ["bpasero"]}, "font-rendering": {"assign": []}, "formatting": {"assign": []}, + "getting-started": {"assign": ["bhavyaus"]}, "ghost-text": {"assign": ["hediet"]}, "git": {"assign": ["lszomoru"]}, "gpu": {"assign": ["deepak1556"]}, From a24677d00e8bb8c2dc85ce568ff816f4a2bfff70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 17 Nov 2022 07:27:04 -0800 Subject: [PATCH 44/67] fixes #166552 (#166594) --- src/vs/base/browser/ui/tree/abstractTree.ts | 11 +++++++---- src/vs/base/browser/ui/tree/dataTree.ts | 2 +- src/vs/base/browser/ui/tree/indexTree.ts | 2 +- src/vs/base/browser/ui/tree/objectTree.ts | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 8298616b75e..20c3c4d2281 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1266,11 +1266,10 @@ class TreeNodeListMouseController extends MouseController< } if (node.collapsible) { - const model = this.tree.model; // internal - const location = model.getNodeLocation(node); + const location = this.tree.getNodeLocation(node); const recursive = e.browserEvent.altKey; this.tree.setFocus([location]); - model.setCollapsed(location, undefined, recursive); + this.tree.toggleCollapsed(location, recursive); if (expandOnlyOnTwistieClick && onTwistie) { return; @@ -1389,7 +1388,7 @@ export abstract class AbstractTree implements IDisposable protected view: TreeNodeList; private renderers: TreeRenderer[]; - model: ITreeModel; // used in MouseController + protected model: ITreeModel; private focus: Trait; private selection: Trait; private anchor: Trait; @@ -1666,6 +1665,10 @@ export abstract class AbstractTree implements IDisposable return this.model.getNode(location); } + getNodeLocation(node: ITreeNode): TRef { + return this.model.getNodeLocation(node); + } + collapse(location: TRef, recursive: boolean = false): boolean { return this.model.setCollapsed(location, true, recursive); } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 6dd8cc19275..06b8e2339ec 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -16,7 +16,7 @@ export interface IDataTreeOptions extends IAbstractTreeOp export class DataTree extends AbstractTree { - declare model: ObjectTreeModel; + protected declare model: ObjectTreeModel; private input: TInput | undefined; private identityProvider: IIdentityProvider | undefined; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 2e7c4d87688..cf86858ca51 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -14,7 +14,7 @@ export interface IIndexTreeOptions extends IAbstractTreeO export class IndexTree extends AbstractTree { - declare model: IndexTreeModel; + protected declare model: IndexTreeModel; constructor( user: string, diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index c1fab1e99d5..c6be2d0f4ff 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -45,7 +45,7 @@ export interface IObjectTreeViewState { export class ObjectTree, TFilterData = void> extends AbstractTree { - declare model: IObjectTreeModel; + protected declare model: IObjectTreeModel; override get onDidChangeCollapseState(): Event> { return this.model.onDidChangeCollapseState; } @@ -197,7 +197,7 @@ export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptio export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider { - declare model: CompressibleObjectTreeModel; + protected declare model: CompressibleObjectTreeModel; constructor( user: string, From dff6f568a1b08a2ddfe5e64c1943b863c5bbfaa1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 17 Nov 2022 16:36:54 +0100 Subject: [PATCH 45/67] GitHub - fix dialog when user does not have permissions to commit (#166595) --- extensions/github/src/pushErrorHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/github/src/pushErrorHandler.ts b/extensions/github/src/pushErrorHandler.ts index c337fc5960a..7c3da24e8e0 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -18,9 +18,9 @@ export function isInCodespaces(): boolean { async function handlePushError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise { const yes = l10n.t('Create Fork'); const no = l10n.t('No'); - const askFork = l10n.t('You don\'t have permissions to push to "{0}/{1}" on GitHub.Would you like to create a fork and push to it instead?', owner, repo); + const askFork = l10n.t('You don\'t have permissions to push to "{0}/{1}" on GitHub. Would you like to create a fork and push to it instead?', owner, repo); - const answer = await window.showInformationMessage(askFork, yes, no); + const answer = await window.showWarningMessage(askFork, { modal: true }, yes, no); if (answer !== yes) { return; } From 001abb14c8eeb69088b416baa354b89627595678 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 17 Nov 2022 16:46:51 +0100 Subject: [PATCH 46/67] remote tunnel: extract log from LogOutputChannels & EnvService (#166597) --- src/vs/platform/environment/common/environment.ts | 3 --- .../environment/common/environmentService.ts | 3 --- src/vs/platform/remoteTunnel/common/remoteTunnel.ts | 5 ++++- .../electron-browser/remoteTunnelService.ts | 6 ++++-- .../workbench/contrib/logs/common/logConstants.ts | 1 - .../contrib/logs/common/logs.contribution.ts | 1 - .../electron-sandbox/remoteTunnel.contribution.ts | 13 ++++++++----- .../environment/browser/environmentService.ts | 3 --- 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 2ec49007e19..f41968d9b0e 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -69,9 +69,6 @@ export interface IEnvironmentService { editSessionId?: string; editSessionsLogResource: URI; - // remote tunnel - remoteTunnelLogResource: URI; - // --- extension development debugExtensionHost: IExtensionHostDebugParams; isExtensionDevelopment: boolean; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 1745ed81462..87401e16abe 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -85,9 +85,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get editSessionsLogResource(): URI { return URI.file(join(this.logsPath, 'editSessions.log')); } - @memoize - get remoteTunnelLogResource(): URI { return URI.file(join(this.logsPath, 'remoteTunnel.log')); } - @memoize get sync(): 'on' | 'off' | undefined { return this.args.sync; } diff --git a/src/vs/platform/remoteTunnel/common/remoteTunnel.ts b/src/vs/platform/remoteTunnel/common/remoteTunnel.ts index 955572b5314..f92e5a0f563 100644 --- a/src/vs/platform/remoteTunnel/common/remoteTunnel.ts +++ b/src/vs/platform/remoteTunnel/common/remoteTunnel.ts @@ -6,7 +6,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; - export interface IRemoteTunnelAccount { readonly authenticationProviderId: string; readonly token: string; @@ -61,3 +60,7 @@ export interface ConnectionInfo { export const CONFIGURATION_KEY_PREFIX = 'remote.tunnels.access'; export const CONFIGURATION_KEY_HOST_NAME = CONFIGURATION_KEY_PREFIX + '.hostNameOverride'; + +export const LOG_FILE_NAME = 'remoteTunnelService.log'; +export const LOGGER_NAME = 'remoteTunnelService'; +export const LOG_CHANNEL_ID = 'remoteTunnelServiceLog'; diff --git a/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts index d4a1c1360ff..20b8ec756ce 100644 --- a/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CONFIGURATION_KEY_HOST_NAME, ConnectionInfo, IRemoteTunnelAccount, IRemoteTunnelService, TunnelStates, TunnelStatus } from 'vs/platform/remoteTunnel/common/remoteTunnel'; +import { CONFIGURATION_KEY_HOST_NAME, ConnectionInfo, IRemoteTunnelAccount, IRemoteTunnelService, LOGGER_NAME, LOG_FILE_NAME, TunnelStates, TunnelStatus } from 'vs/platform/remoteTunnel/common/remoteTunnel'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -18,6 +18,7 @@ import { ISharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-b import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { hostname, homedir } from 'os'; +import { URI } from 'vs/base/common/uri'; type RemoteTunnelEnablementClassification = { owner: 'aeschli'; @@ -65,7 +66,8 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); - this._logger = this._register(loggerService.createLogger(environmentService.remoteTunnelLogResource, { name: 'remoteTunnel' })); + const remoteTunnelLogResource = URI.file(join(environmentService.logsPath, LOG_FILE_NAME)); + this._logger = this._register(loggerService.createLogger(remoteTunnelLogResource, { name: LOGGER_NAME })); this._startTunnelProcessDelayer = new Delayer(100); this._register(sharedProcessLifecycleService.onWillShutdown(e => { diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts index 49139de524d..a9869d06fc7 100644 --- a/src/vs/workbench/contrib/logs/common/logConstants.ts +++ b/src/vs/workbench/contrib/logs/common/logConstants.ts @@ -10,7 +10,6 @@ export const telemetryLogChannelId = 'telemetryLog'; export const extensionTelemetryLogChannelId = 'extensionTelemetryLog'; export const userDataSyncLogChannelId = 'userDataSyncLog'; export const editSessionsLogChannelId = 'editSessionsSyncLog'; -export const remoteTunnelLogChannelId = 'remoteTunnelLog'; export const remoteServerLog = 'remoteServerLog'; export const remotePtyHostLog = 'remotePtyHostLog'; diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index ca6f046c7cf..07f420f6731 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -50,7 +50,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { private registerCommonContributions(): void { this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Settings Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.editSessionsLogChannelId, nls.localize('editSessionsLog', "Edit Sessions"), this.environmentService.editSessionsLogResource); - this.registerLogChannel(Constants.remoteTunnelLogChannelId, nls.localize('remoteTunnelLog', "Remote Tunnel"), this.environmentService.remoteTunnelLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); const registerTelemetryChannel = () => { diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index 1d0ffffb7e4..105dcf61c0e 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -6,7 +6,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IProductService } from 'vs/platform/product/common/productService'; -import { CONFIGURATION_KEY_HOST_NAME, CONFIGURATION_KEY_PREFIX, ConnectionInfo, IRemoteTunnelService } from 'vs/platform/remoteTunnel/common/remoteTunnel'; +import { CONFIGURATION_KEY_HOST_NAME, CONFIGURATION_KEY_PREFIX, ConnectionInfo, IRemoteTunnelService, LOGGER_NAME, LOG_CHANNEL_ID, LOG_FILE_NAME } from 'vs/platform/remoteTunnel/common/remoteTunnel'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { localize } from 'vs/nls'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -21,7 +21,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IStringDictionary } from 'vs/base/common/collections'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IOutputService, registerLogChannel } from 'vs/workbench/services/output/common/output'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IProgress, IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -32,11 +32,11 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Action } from 'vs/base/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; +import { join } from 'vs/base/common/path'; export const REMOTE_TUNNEL_CATEGORY: ILocalizedString = { original: 'Remote Tunnels', @@ -106,7 +106,9 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo ) { super(); - this.logger = this._register(loggerService.createLogger(environmentService.remoteTunnelLogResource, { name: 'remoteTunnel' })); + const remoteTunnelServiceLogResource = URI.file(join(environmentService.logsPath, LOG_FILE_NAME)); + + this.logger = this._register(loggerService.createLogger(remoteTunnelServiceLogResource, { name: LOGGER_NAME })); this.connectionStateContext = REMOTE_TUNNEL_CONNECTION_STATE.bindTo(this.contextKeyService); @@ -153,6 +155,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo this.initialize(true); } + registerLogChannel(LOG_CHANNEL_ID, localize('remoteTunnelLog', "Remote Tunnel Service"), remoteTunnelServiceLogResource, fileService, logService); } private get existingSessionId() { @@ -542,7 +545,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo async run(accessor: ServicesAccessor) { const outputService = accessor.get(IOutputService); - outputService.showChannel(Constants.remoteTunnelLogChannelId); + outputService.showChannel(LOG_CHANNEL_ID); } })); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 9447f7882f7..17b1d82de09 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -111,9 +111,6 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi @memoize get editSessionsLogResource(): URI { return joinPath(this.logsHome, 'editSessions.log'); } - @memoize - get remoteTunnelLogResource(): URI { return joinPath(this.logsHome, 'remoteTunnel.log'); } - @memoize get sync(): 'on' | 'off' | undefined { return undefined; } From 0d4e84d528026a54a9feb1e3bd59e3be2f39f03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 17 Nov 2022 07:57:29 -0800 Subject: [PATCH 47/67] fix log message (#166598) related to #163418 --- src/vs/platform/policy/node/nativePolicyService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 39e2f126cfe..af5957fe7df 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -23,7 +23,7 @@ export class NativePolicyService extends AbstractPolicyService implements IPolic } protected async _updatePolicyDefinitions(policyDefinitions: IStringDictionary): Promise { - this.logService.trace(`NativePolicyService#_updatePolicyDefinitions - Found ${policyDefinitions.length} policy definitions`); + this.logService.trace(`NativePolicyService#_updatePolicyDefinitions - Found ${Object.keys(policyDefinitions).length} policy definitions`); await this.throttler.queue(() => new Promise((c, e) => { try { From 1a0223da3f0181685257812e23590811a0096af8 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 17:19:29 +0100 Subject: [PATCH 48/67] fix node tests --- test/unit/node/index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/unit/node/index.js b/test/unit/node/index.js index 1fb4fd57020..cea515286a0 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -56,6 +56,22 @@ if (majorRequiredNodeVersion !== currentMajorNodeVersion) { } function main() { + + // VSCODE_GLOBALS: node_modules + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { + get(target, mod) { + if (!target[mod] && typeof mod === 'string') { + target[mod] = require(mod); + } + return target[mod]; + } + }); + + // VSCODE_GLOBALS: package/product.json + globalThis._VSCODE_PRODUCT_JSON = require(`${REPO_ROOT}/product.json`); + globalThis._VSCODE_PACKAGE_JSON = require(`${REPO_ROOT}/package.json`); + + process.on('uncaughtException', function (e) { console.error(e.stack || e); }); From 1595c5b63a5f428ebdade6e71646cd327c85ad0e Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 17:49:43 +0100 Subject: [PATCH 49/67] define globals for server-main too --- src/server-main.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/server-main.js b/src/server-main.js index 27ecbaed0d6..a83131d265e 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -258,6 +258,19 @@ function loadCode() { return new Promise((resolve, reject) => { const path = require('path'); + // VSCODE_GLOBALS: node_modules + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { + get(target, mod) { + if (!target[mod] && typeof mod === 'string') { + target[mod] = require(mod); + } + return target[mod]; + } + }); + // VSCODE_GLOBALS: package/product.json + globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); + globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); + delete process.env['ELECTRON_RUN_AS_NODE']; // Keep bootstrap-amd.js from redefining 'fs'. // See https://github.com/microsoft/vscode-remote-release/issues/6543 From 6dd2726c73d9f8ebafa867c6135da95cc63cb8be Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 Nov 2022 17:58:51 +0100 Subject: [PATCH 50/67] simplify `_VSCODE_NODE_MODULES` util --- src/bootstrap-fork.js | 9 +-------- src/bootstrap-window.js | 9 +-------- src/main.js | 9 +-------- src/server-main.js | 10 ++-------- test/unit/electron/renderer.js | 10 ++-------- test/unit/node/index.js | 9 +-------- 6 files changed, 8 insertions(+), 48 deletions(-) diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 6d11c4a57e8..00bf5ed7451 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -38,14 +38,7 @@ if (process.env['VSCODE_PARENT_PID']) { } // VSCODE_GLOBALS: node_modules -globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { - get(target, mod) { - if (!target[mod] && typeof mod === 'string') { - target[mod] = require(mod); - } - return target[mod]; - } -}); +globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); // VSCODE_GLOBALS: package/product.json globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 40a2ff21a49..e4218117b7d 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -113,14 +113,7 @@ window['MonacoEnvironment'] = {}; // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { - get(target, mod) { - if (!target[mod] && typeof mod === 'string') { - target[mod] = (require.__$__nodeRequire ?? require)(mod); - } - return target[mod]; - } - }); + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => (require.__$__nodeRequire ?? require)(String(mod)) }); // VSCODE_GLOBALS: package/product.json globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)(configuration.appRoot + '/product.json'); diff --git a/src/main.js b/src/main.js index c40595aa75d..eebc049c6f5 100644 --- a/src/main.js +++ b/src/main.js @@ -142,14 +142,7 @@ function startup(codeCachePath, nlsConfig) { process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || ''; // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { - get(target, mod) { - if (!target[mod] && typeof mod === 'string') { - target[mod] = require(mod); - } - return target[mod]; - } - }); + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); // VSCODE_GLOBALS: package/product.json globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); diff --git a/src/server-main.js b/src/server-main.js index a83131d265e..8eb66e83abf 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -259,14 +259,8 @@ function loadCode() { const path = require('path'); // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { - get(target, mod) { - if (!target[mod] && typeof mod === 'string') { - target[mod] = require(mod); - } - return target[mod]; - } - }); + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); + // VSCODE_GLOBALS: package/product.json globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 77a10b3421d..a91725887a5 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -73,14 +73,8 @@ if (util.inspect && util.inspect['defaultOptions']) { } // VSCODE_GLOBALS: node_modules -globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { - get(target, mod) { - if (!target[mod] && typeof mod === 'string') { - target[mod] = (require.__$__nodeRequire ?? require)(mod); - } - return target[mod]; - } -}); +globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => (require.__$__nodeRequire ?? require)(String(mod)) }); + // VSCODE_GLOBALS: package/product.json globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)('../../../product.json'); globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)('../../../package.json'); diff --git a/test/unit/node/index.js b/test/unit/node/index.js index cea515286a0..c6474162dfc 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -58,14 +58,7 @@ if (majorRequiredNodeVersion !== currentMajorNodeVersion) { function main() { // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { - get(target, mod) { - if (!target[mod] && typeof mod === 'string') { - target[mod] = require(mod); - } - return target[mod]; - } - }); + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); // VSCODE_GLOBALS: package/product.json globalThis._VSCODE_PRODUCT_JSON = require(`${REPO_ROOT}/product.json`); From 1ac5ea41e191611f130e0ae216e4a5dd1966f1f5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 17 Nov 2022 18:15:34 +0100 Subject: [PATCH 51/67] Implements handled undo stack in merge editor (#166606) --- src/vs/editor/common/model.ts | 5 + src/vs/editor/common/model/editStack.ts | 12 +- src/vs/editor/common/model/textModel.ts | 10 +- .../mergeEditor/browser/model/editing.ts | 48 ++++---- .../browser/model/mergeEditorModel.ts | 115 +++++++++++++++--- .../browser/model/textModelDiffs.ts | 11 +- .../mergeEditor/browser/view/viewModel.ts | 105 ++++++++++++++-- 7 files changed, 241 insertions(+), 65 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 0d2d8f3576f..861ad5b5832 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -21,6 +21,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides'; import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; +import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; /** * Vertical Lane in the overview ruler of the editor. @@ -1023,6 +1024,10 @@ export interface ITextModel { * @return The cursor state returned by the `cursorStateComputer`. */ pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; + /** + * @internal + */ + pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer, group?: UndoRedoGroup): Selection[] | null; /** * Change the end of line sequence. This is the preferred way of diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index c1fd64e880d..8c9f1c4d6e2 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Selection } from 'vs/editor/common/core/selection'; import { EndOfLineSequence, ICursorStateComputer, IValidEditOperation, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/core/textChange'; import * as buffer from 'vs/base/common/buffer'; @@ -408,24 +408,24 @@ export class EditStack { this._undoRedoService.removeElements(this._model.uri); } - private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement { + private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null, group: UndoRedoGroup | undefined): EditStackElement { const lastElement = this._undoRedoService.getLastElement(this._model.uri); if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) { return lastElement; } const newElement = new SingleModelEditStackElement(nls.localize('edit', "Typing"), 'undoredo.textBufferEdit', this._model, beforeCursorState); - this._undoRedoService.pushElement(newElement); + this._undoRedoService.pushElement(newElement, group); return newElement; } public pushEOL(eol: EndOfLineSequence): void { - const editStackElement = this._getOrCreateEditStackElement(null); + const editStackElement = this._getOrCreateEditStackElement(null, undefined); this._model.setEOL(eol); editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null); } - public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { - const editStackElement = this._getOrCreateEditStackElement(beforeCursorState); + public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null, group?: UndoRedoGroup): Selection[] | null { + const editStackElement = this._getOrCreateEditStackElement(beforeCursorState, group); const inverseEditOperations = this._model.applyEdits(editOperations, true); const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange })); diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index f22215997fb..9447ad43570 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -42,7 +42,7 @@ import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptions import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides'; import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart'; import { IColorTheme, ThemeColor } from 'vs/platform/theme/common/themeService'; -import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; export function createTextBufferFactory(text: string): model.ITextBufferFactory { const builder = new PieceTreeTextBufferBuilder(); @@ -1242,18 +1242,18 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati return result; } - public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup): Selection[] | null { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer); + return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer, group); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup): Selection[] | null { if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { // Go through each saved line number and insert a trim whitespace edit // if it is safe to do so (no conflicts with other edits). @@ -1340,7 +1340,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati if (this._initialUndoRedoSnapshot === null) { this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri); } - return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); + return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer, group); } _applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/editing.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/editing.ts index de2e9e2c6df..57775b6e7aa 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/editing.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/editing.ts @@ -5,7 +5,7 @@ import { equals } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { LineRange } from './lineRange'; /** @@ -22,8 +22,8 @@ export class LineRangeEdit { return this.range.equals(other.range) && equals(this.newLines, other.newLines); } - public apply(model: ITextModel): void { - new LineEdits([this]).apply(model); + public toEdits(modelLineCount: number): IIdentifiedSingleEditOperation[] { + return new LineEdits([this]).toEdits(modelLineCount); } } @@ -41,30 +41,26 @@ export class RangeEdit { export class LineEdits { constructor(public readonly edits: readonly LineRangeEdit[]) { } - public apply(model: ITextModel): void { - model.pushEditOperations( - null, - this.edits.map((e) => { - if (e.range.endLineNumberExclusive <= model.getLineCount()) { - return { - range: new Range(e.range.startLineNumber, 1, e.range.endLineNumberExclusive, 1), - text: e.newLines.map(s => s + '\n').join(''), - }; - } - - if (e.range.startLineNumber === 1) { - return { - range: new Range(1, 1, model.getLineCount(), Number.MAX_SAFE_INTEGER), - text: e.newLines.join('\n'), - }; - } - + public toEdits(modelLineCount: number): IIdentifiedSingleEditOperation[] { + return this.edits.map((e) => { + if (e.range.endLineNumberExclusive <= modelLineCount) { return { - range: new Range(e.range.startLineNumber - 1, Number.MAX_SAFE_INTEGER, model.getLineCount(), Number.MAX_SAFE_INTEGER), - text: e.newLines.map(s => '\n' + s).join(''), + range: new Range(e.range.startLineNumber, 1, e.range.endLineNumberExclusive, 1), + text: e.newLines.map(s => s + '\n').join(''), }; - }), - () => null - ); + } + + if (e.range.startLineNumber === 1) { + return { + range: new Range(1, 1, modelLineCount, Number.MAX_SAFE_INTEGER), + text: e.newLines.join('\n'), + }; + } + + return { + range: new Range(e.range.startLineNumber - 1, Number.MAX_SAFE_INTEGER, modelLineCount, Number.MAX_SAFE_INTEGER), + text: e.newLines.map(s => '\n' + s).join(''), + }; + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index e3bbbd7c81f..7d8f404b794 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -6,10 +6,13 @@ import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { autorunHandleChanges, derived, IObservable, IReader, ISettableObservable, ITransaction, keepAlive, observableValue, transaction, waitForState } from 'vs/base/common/observable'; +import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; +import { localize } from 'vs/nls'; +import { IResourceUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { IMergeDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; @@ -58,6 +61,7 @@ export class MergeEditorModel extends EditorModel { public readonly telemetry: MergeEditorTelemetry, @IModelService private readonly modelService: IModelService, @ILanguageService private readonly languageService: ILanguageService, + @IUndoRedoService private readonly undoRedoService: IUndoRedoService, ) { super(); @@ -405,9 +409,9 @@ export class MergeEditorModel extends EditorModel { public setState( baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState, - markInputAsHandled: boolean | InputNumber, - transaction: ITransaction, - pushStackElement: boolean = false + _markInputAsHandled: boolean | InputNumber, + tx: ITransaction, + _pushStackElement: boolean = false ): void { if (!this.isUpToDate.get()) { throw new BugIndicatingError('Cannot set state while updating'); @@ -421,29 +425,36 @@ export class MergeEditorModel extends EditorModel { const conflictingDiffs = this.resultTextModelDiffs.findTouchingDiffs( baseRange.baseRange ); + const group = new UndoRedoGroup(); if (conflictingDiffs) { - this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction); + this.resultTextModelDiffs.removeDiffs(conflictingDiffs, tx, group); } const { edit, effectiveState } = baseRange.getEditForBase(state); - existingState.accepted.set(effectiveState, transaction); + existingState.accepted.set(effectiveState, tx); existingState.previousNonDiffingState = undefined; existingState.computedFromDiffing = false; + const input1Handled = existingState.handledInput1.get(); + const input2Handled = existingState.handledInput2.get(); + + if (!input1Handled || !input2Handled) { + this.undoRedoService.pushElement( + new MarkAsHandledUndoRedoElement(this.resultTextModel.uri, new WeakRef(this), new WeakRef(existingState), input1Handled, input2Handled), + group + ); + } + if (edit) { - if (pushStackElement) { - this.resultTextModel.pushStackElement(); - } - this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, transaction); - if (pushStackElement) { - this.resultTextModel.pushStackElement(); - } + this.resultTextModel.pushStackElement(); + this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, tx, group); + this.resultTextModel.pushStackElement(); } // always set conflict as handled - existingState.handledInput1.set(true, transaction); - existingState.handledInput2.set(true, transaction); + existingState.handledInput1.set(true, tx); + existingState.handledInput2.set(true, tx); } public resetDirtyConflictsToBase(): void { @@ -474,6 +485,42 @@ export class MergeEditorModel extends EditorModel { return; } + const dataRef = new WeakRef(ModifiedBaseRangeData); + const modelRef = new WeakRef(this); + + this.undoRedoService.pushElement({ + type: UndoRedoElementType.Resource, + resource: this.resultTextModel.uri, + code: 'setInputHandled', + label: localize('setInputHandled', "Set Input Handled"), + redo() { + const model = modelRef.deref(); + const data = dataRef.deref(); + if (model && !model.isDisposed() && data) { + transaction(tx => { + if (inputNumber === 1) { + state.handledInput1.set(handled, tx); + } else { + state.handledInput2.set(handled, tx); + } + }); + } + }, + undo() { + const model = modelRef.deref(); + const data = dataRef.deref(); + if (model && !model.isDisposed() && data) { + transaction(tx => { + if (inputNumber === 1) { + state.handledInput1.set(!handled, tx); + } else { + state.handledInput2.set(!handled, tx); + } + }); + } + }, + }); + if (inputNumber === 1) { state.handledInput1.set(handled, tx); } else { @@ -723,3 +770,43 @@ export const enum MergeEditorModelState { upToDate = 2, updating = 3, } + +class MarkAsHandledUndoRedoElement implements IResourceUndoRedoElement { + public readonly code = 'undoMarkAsHandled'; + public readonly label = localize('undoMarkAsHandled', 'Undo Mark As Handled'); + + public readonly type = UndoRedoElementType.Resource; + + constructor( + public readonly resource: URI, + private readonly mergeEditorModelRef: WeakRef, + private readonly stateRef: WeakRef, + private readonly input1Handled: boolean, + private readonly input2Handled: boolean, + ) { } + + public redo() { + const mergeEditorModel = this.mergeEditorModelRef.deref(); + if (!mergeEditorModel || mergeEditorModel.isDisposed()) { + return; + } + const state = this.stateRef.deref(); + if (!state) { return; } + transaction(tx => { + state.handledInput1.set(true, tx); + state.handledInput2.set(true, tx); + }); + } + public undo() { + const mergeEditorModel = this.mergeEditorModelRef.deref(); + if (!mergeEditorModel || mergeEditorModel.isDisposed()) { + return; + } + const state = this.stateRef.deref(); + if (!state) { return; } + transaction(tx => { + state.handledInput1.set(this.input1Handled, tx); + state.handledInput2.set(this.input2Handled, tx); + }); + } +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index b618ffd82bc..526ca3d4085 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -13,6 +13,7 @@ import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRa import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { IMergeDiffComputer } from './diffComputer'; import { autorun, IObservable, IReader, ITransaction, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; +import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; export class TextModelDiffs extends Disposable { private recomputeCount = 0; @@ -120,7 +121,7 @@ export class TextModelDiffs extends Disposable { } } - public removeDiffs(diffToRemoves: DetailedLineRangeMapping[], transaction: ITransaction | undefined): void { + public removeDiffs(diffToRemoves: DetailedLineRangeMapping[], transaction: ITransaction | undefined, group?: UndoRedoGroup): void { this.ensureUpToDate(); diffToRemoves.sort(compareBy((d) => d.inputRange.startLineNumber, numberComparator)); @@ -137,7 +138,8 @@ export class TextModelDiffs extends Disposable { } this.barrier.runExclusivelyOrThrow(() => { - diffToRemove.getReverseLineEdit().apply(this.textModel); + const edits = diffToRemove.getReverseLineEdit().toEdits(this.textModel.getLineCount()); + this.textModel.pushEditOperations(null, edits, () => null, group); }); diffs = diffs.map((d) => @@ -153,7 +155,7 @@ export class TextModelDiffs extends Disposable { /** * Edit must be conflict free. */ - public applyEditRelativeToOriginal(edit: LineRangeEdit, transaction: ITransaction | undefined): void { + public applyEditRelativeToOriginal(edit: LineRangeEdit, transaction: ITransaction | undefined, group?: UndoRedoGroup): void { this.ensureUpToDate(); const editMapping = new DetailedLineRangeMapping( @@ -191,7 +193,8 @@ export class TextModelDiffs extends Disposable { } this.barrier.runExclusivelyOrThrow(() => { - new LineRangeEdit(edit.range.delta(delta), edit.newLines).apply(this.textModel); + const edits = new LineRangeEdit(edit.range.delta(delta), edit.newLines).toEdits(this.textModel.getLineCount()); + this.textModel.pushEditOperations(null, edits, () => null, group); }); this._diffs.set(newDiffs, transaction, TextModelDiffChangeReason.other); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index 69bdccfa99e..75a2bf57513 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -8,6 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedObservableWithWritableCache, IObservable, IReader, ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; import { Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -24,6 +25,8 @@ export class MergeEditorViewModel extends Disposable { { range: ModifiedBaseRange | undefined; counter: number } >('manuallySetActiveModifiedBaseRange', { range: undefined, counter: 0 }); + private readonly attachedHistory = this._register(new AttachedHistory(this.model.resultTextModel)); + constructor( public readonly model: MergeEditorModel, public readonly inputCodeEditorView1: InputCodeEditorView, @@ -32,24 +35,53 @@ export class MergeEditorViewModel extends Disposable { public readonly baseCodeEditorView: IObservable, public readonly showNonConflictingChanges: IObservable, @IConfigurationService private readonly configurationService: IConfigurationService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, ) { super(); this._register(resultCodeEditorView.editor.onDidChangeModelContent(e => { - if (this.model.isApplyingEditInResult) { + if (this.model.isApplyingEditInResult || e.isRedoing || e.isUndoing) { return; } - transaction(tx => { - /** @description Mark conflicts touched by manual edits as handled */ - for (const change of e.changes) { - const rangeInBase = this.model.translateResultRangeToBase(Range.lift(change.range)); - const baseRanges = this.model.findModifiedBaseRangesInRange(new LineRange(rangeInBase.startLineNumber, rangeInBase.endLineNumber - rangeInBase.startLineNumber)); - if (baseRanges.length === 1) { - this.model.setHandled(baseRanges[0], true, tx); + + const baseRangeStates: ModifiedBaseRange[] = []; + + for (const change of e.changes) { + const rangeInBase = this.model.translateResultRangeToBase(Range.lift(change.range)); + const baseRanges = this.model.findModifiedBaseRangesInRange(new LineRange(rangeInBase.startLineNumber, rangeInBase.endLineNumber - rangeInBase.startLineNumber)); + if (baseRanges.length === 1) { + const isHandled = this.model.isHandled(baseRanges[0]).get(); + if (!isHandled) { + baseRangeStates.push(baseRanges[0]); } } - }); + } + + if (baseRangeStates.length === 0) { + return; + } + + const element = { + model: this.model, + redo() { + transaction(tx => { + /** @description Mark conflicts touched by manual edits as handled */ + for (const r of baseRangeStates) { + this.model.setHandled(r, true, tx); + } + }); + }, + undo() { + transaction(tx => { + /** @description Mark conflicts touched by manual edits as handled */ + for (const r of baseRangeStates) { + this.model.setHandled(r, false, tx); + } + }); + }, + }; + this.attachedHistory.pushAttachedHistoryElement(element); + element.redo(); })); } @@ -255,3 +287,56 @@ export class MergeEditorViewModel extends Disposable { }); } } + +class AttachedHistory extends Disposable { + private readonly attachedHistory: { element: IAttachedHistoryElement; altId: number }[] = []; + private previousAltId: number = this.model.getAlternativeVersionId(); + + constructor(private readonly model: ITextModel) { + super(); + + this._register(model.onDidChangeContent((e) => { + const currentAltId = model.getAlternativeVersionId(); + + if (e.isRedoing) { + for (const item of this.attachedHistory) { + if (this.previousAltId < item.altId && item.altId <= currentAltId) { + item.element.redo(); + } + } + } else if (e.isUndoing) { + for (let i = this.attachedHistory.length - 1; i >= 0; i--) { + const item = this.attachedHistory[i]; + if (currentAltId < item.altId && item.altId <= this.previousAltId) { + item.element.undo(); + } + } + + } else { + // The user destroyed the redo stack by performing a non redo/undo operation. + // Thus we also need to remove all history elements after the last version id. + while ( + this.attachedHistory.length > 0 + && this.attachedHistory[this.attachedHistory.length - 1]!.altId > this.previousAltId + ) { + this.attachedHistory.pop(); + } + } + + this.previousAltId = currentAltId; + })); + } + + /** + * Pushes an history item that is tied to the last text edit (or an extension of it). + * When the last text edit is undone/redone, so is is this history item. + */ + public pushAttachedHistoryElement(element: IAttachedHistoryElement): void { + this.attachedHistory.push({ altId: this.model.getAlternativeVersionId(), element }); + } +} + +interface IAttachedHistoryElement { + undo(): void; + redo(): void; +} From 888fd835ca18a3b2668a91b404e188cd05bbcb99 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 17 Nov 2022 10:16:13 -0800 Subject: [PATCH 52/67] don't clean attachments for a cell that is removed --- extensions/ipynb/src/notebookAttachmentCleaner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ipynb/src/notebookAttachmentCleaner.ts b/extensions/ipynb/src/notebookAttachmentCleaner.ts index a72761754df..c7af81492da 100644 --- a/extensions/ipynb/src/notebookAttachmentCleaner.ts +++ b/extensions/ipynb/src/notebookAttachmentCleaner.ts @@ -187,7 +187,7 @@ export class AttachmentCleaner implements vscode.CodeActionProvider { } } - if (!objectEquals(markdownAttachmentsInUse, cell.metadata.attachments)) { + if (cell.index > -1 && !objectEquals(markdownAttachmentsInUse, cell.metadata.attachments)) { const updateMetadata: { [key: string]: any } = deepClone(cell.metadata); updateMetadata.attachments = markdownAttachmentsInUse; const metadataEdit = vscode.NotebookEdit.updateCellMetadata(cell.index, updateMetadata); From c3f40b94360bb76f9ae4bd385fe82ad7cb4b0542 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 17 Nov 2022 10:25:28 -0800 Subject: [PATCH 53/67] Switch from setupCustomHover to hoverService (#166500) Ref #159088 By switching to hoverService, we get to customize the hover widget more for the Settings editor indicators. --- src/vs/base/common/async.ts | 2 +- .../browser/media/settingsEditor2.css | 7 +- .../settingsEditorSettingIndicators.ts | 234 ++++++++++-------- .../contrib/preferences/common/preferences.ts | 1 - 4 files changed, 142 insertions(+), 102 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 232375b7f10..910af8de46a 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -819,7 +819,7 @@ export class IntervalTimer implements IDisposable { } } -export class RunOnceScheduler { +export class RunOnceScheduler implements IDisposable { protected runner: ((...args: unknown[]) => void) | null; diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 00cfefb81a0..de3754709ab 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -344,6 +344,7 @@ overflow: hidden; text-overflow: ellipsis; display: inline-block; /* size to contents for hover to show context button */ + padding-bottom: 2px; /* so that focus outlines wrap around nicely for indicators, especially ones with codicons */ } .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-modified-indicator { @@ -368,7 +369,7 @@ bottom: 23px; } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label { +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .setting-indicators-container { font-style: italic; } @@ -379,8 +380,8 @@ opacity: 0.9; } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-indicator:hover { - text-decoration: underline; +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .setting-indicators-container .setting-indicator { + padding-bottom: 2px; } .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .codicon { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index fba13a6f9b4..9c5daa698b7 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, ITooltipMarkdownString, IUpdatableHoverOptions, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; +import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; -import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -18,8 +21,8 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { getDefaultIgnoredSettings, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; -import { MODIFIED_INDICATOR_USE_INLINE_ONLY, POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; const $ = DOM.$; @@ -34,7 +37,7 @@ export interface ISettingOverrideClickEvent { interface SettingIndicator { element: HTMLElement; label: SimpleIconLabel; - hover: MutableDisposable; + disposables: DisposableStore; } /** @@ -42,7 +45,6 @@ interface SettingIndicator { */ export class SettingsTreeIndicatorsLabel implements IDisposable { private indicatorsContainerElement: HTMLElement; - private hoverDelegate: IHoverDelegate; private workspaceTrustIndicator: SettingIndicator; private scopeOverridesIndicator: SettingIndicator; @@ -53,24 +55,15 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { constructor( container: HTMLElement, - @IConfigurationService configurationService: IConfigurationService, - @IHoverService hoverService: IHoverService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IHoverService private readonly hoverService: IHoverService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ILanguageService private readonly languageService: ILanguageService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @ICommandService private readonly commandService: ICommandService) { - this.indicatorsContainerElement = DOM.append(container, $('.misc-label')); + this.indicatorsContainerElement = DOM.append(container, $('.setting-indicators-container')); this.indicatorsContainerElement.style.display = 'inline'; - this.hoverDelegate = { - showHover: (options: IHoverDelegateOptions, focus?: boolean) => { - return hoverService.showHover(options, focus); - }, - onDidHideHover: () => { }, - delay: configurationService.getValue('workbench.hover.delay'), - placement: 'element' - }; - this.profilesEnabled = this.userDataProfilesService.isEnabled(); this.workspaceTrustIndicator = this.createWorkspaceTrustIndicator(); @@ -79,72 +72,116 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.defaultOverrideIndicator = this.createDefaultOverrideIndicator(); } + private defaultHoverOptions: Partial = { + hoverPosition: HoverPosition.BELOW, + showPointer: true, + compact: false + }; + + private addHoverDisposables(disposables: DisposableStore, element: HTMLElement, showHover: (focus: boolean) => IHoverWidget | undefined) { + disposables.clear(); + const scheduler: RunOnceScheduler = disposables.add(new RunOnceScheduler(() => { + const hover = showHover(false); + if (hover) { + disposables.add(hover); + } + }, this.configurationService.getValue('workbench.hover.delay'))); + disposables.add(DOM.addDisposableListener(element, DOM.EventType.MOUSE_OVER, () => { + if (!scheduler.isScheduled()) { + scheduler.schedule(); + } + })); + disposables.add(DOM.addDisposableListener(element, DOM.EventType.MOUSE_LEAVE, () => { + scheduler.cancel(); + })); + disposables.add(DOM.addDisposableListener(element, DOM.EventType.KEY_DOWN, (e) => { + const evt = new StandardKeyboardEvent(e); + if (evt.equals(KeyCode.Space) || evt.equals(KeyCode.Enter)) { + const hover = showHover(true); + if (hover) { + disposables.add(hover); + } + e.preventDefault(); + } + })); + } + private createWorkspaceTrustIndicator(): SettingIndicator { const workspaceTrustElement = $('span.setting-indicator.setting-item-workspace-trust'); + workspaceTrustElement.tabIndex = 0; const workspaceTrustLabel = new SimpleIconLabel(workspaceTrustElement); workspaceTrustLabel.text = '$(warning) ' + localize('workspaceUntrustedLabel', "Setting value not applied"); - const contentFallback = localize('trustLabel', "The setting value can only be applied in a trusted workspace."); - const contentMarkdownString = contentFallback + ` [${localize('manageWorkspaceTrust', "Manage Workspace Trust")}](manage-workspace-trust).`; - const content: ITooltipMarkdownString = { - markdown: { - value: contentMarkdownString, - isTrusted: false, - supportHtml: false - }, - markdownNotSupportedFallback: contentFallback + const content = localize('trustLabel', "The setting value can only be applied in a trusted workspace."); + const disposables = new DisposableStore(); + const showHover = (focus: boolean) => { + return this.hoverService.showHover({ + ...this.defaultHoverOptions, + content, + target: workspaceTrustElement, + actions: [{ + label: localize('manageWorkspaceTrust', "Manage Workspace Trust"), + commandId: 'workbench.trust.manage', + run: (target: HTMLElement) => { + this.commandService.executeCommand('workbench.trust.manage'); + } + }], + }, focus); }; - - const hover = new MutableDisposable(); - const options: IUpdatableHoverOptions = { - linkHandler: (url: string) => { - this.commandService.executeCommand('workbench.trust.manage'); - hover.value?.hide(); - } - }; - hover.value = setupCustomHover(this.hoverDelegate, workspaceTrustElement, content, options); + this.addHoverDisposables(disposables, workspaceTrustElement, showHover); return { element: workspaceTrustElement, label: workspaceTrustLabel, - hover + disposables }; } private createScopeOverridesIndicator(): SettingIndicator { // Don't add .setting-indicator class here, because it gets conditionally added later. const otherOverridesElement = $('span.setting-item-overrides'); + otherOverridesElement.tabIndex = 0; const otherOverridesLabel = new SimpleIconLabel(otherOverridesElement); return { element: otherOverridesElement, label: otherOverridesLabel, - hover: new MutableDisposable() + disposables: new DisposableStore() }; } private createSyncIgnoredIndicator(): SettingIndicator { const syncIgnoredElement = $('span.setting-indicator.setting-item-ignored'); + syncIgnoredElement.tabIndex = 0; const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced'); const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync"); - const hover = new MutableDisposable(); - hover.value = setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent); + const disposables = new DisposableStore(); + const showHover = (focus: boolean) => { + return this.hoverService.showHover({ + ...this.defaultHoverOptions, + content: syncIgnoredHoverContent, + target: syncIgnoredElement + }, focus); + }; + this.addHoverDisposables(disposables, syncIgnoredElement, showHover); + return { element: syncIgnoredElement, label: syncIgnoredLabel, - hover + disposables: new DisposableStore() }; } private createDefaultOverrideIndicator(): SettingIndicator { const defaultOverrideIndicator = $('span.setting-indicator.setting-item-default-overridden'); + defaultOverrideIndicator.tabIndex = 0; const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator); defaultOverrideLabel.text = localize('defaultOverriddenLabel', "Default value changed"); return { element: defaultOverrideIndicator, label: defaultOverrideLabel, - hover: new MutableDisposable() + disposables: new DisposableStore() }; } @@ -193,7 +230,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { const indicators = [this.workspaceTrustIndicator, this.scopeOverridesIndicator, this.syncIgnoredIndicator, this.defaultOverrideIndicator]; for (const indicator of indicators) { - indicator.hover.dispose(); + indicator.disposables.dispose(); } } @@ -206,24 +243,22 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.scopeOverridesIndicator.element.classList.add('setting-indicator'); this.scopeOverridesIndicator.label.text = '$(warning) ' + localize('policyLabelText', "Setting value not applied"); - const contentFallback = localize('policyDescription', "This setting is managed by your organization and its applied value cannot be changed."); - const contentMarkdownString = contentFallback + ` [${localize('policyFilterLink', "View policy settings")}](policy-settings).`; - const content: ITooltipMarkdownString = { - markdown: { - value: contentMarkdownString, - isTrusted: false, - supportHtml: false - }, - markdownNotSupportedFallback: contentFallback + const content = localize('policyDescription', "This setting is managed by your organization and its applied value cannot be changed."); + const showHover = (focus: boolean) => { + return this.hoverService.showHover({ + ...this.defaultHoverOptions, + content, + actions: [{ + label: localize('policyFilterLink', "View policy settings"), + commandId: '_settings.action.viewPolicySettings', + run: (_) => { + onApplyFilter.fire(`@${POLICY_SETTING_TAG}`); + } + }], + target: this.scopeOverridesIndicator.element + }, focus); }; - const options: IUpdatableHoverOptions = { - linkHandler: _ => { - onApplyFilter.fire(`@${POLICY_SETTING_TAG}`); - this.scopeOverridesIndicator.hover.value?.hide(); - } - }; - this.scopeOverridesIndicator.hover.value = setupCustomHover(this.hoverDelegate, this.scopeOverridesIndicator.element, content, options); - + this.addHoverDisposables(this.scopeOverridesIndicator.disposables, this.scopeOverridesIndicator.element, showHover); } else if (this.profilesEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) { // If the setting is an application-scoped setting, there are no overrides so we can use this // indicator to display that information instead. @@ -234,16 +269,19 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.scopeOverridesIndicator.label.text = applicationSettingText; const content = localize('applicationSettingDescription', "The setting is not specific to the current profile, and will retain its value when switching profiles."); - this.scopeOverridesIndicator.hover.value = setupCustomHover(this.hoverDelegate, this.scopeOverridesIndicator.element, content); + const showHover = (focus: boolean) => { + return this.hoverService.showHover({ + ...this.defaultHoverOptions, + content, + target: this.scopeOverridesIndicator.element + }, focus); + }; + this.addHoverDisposables(this.scopeOverridesIndicator.disposables, this.scopeOverridesIndicator.element, showHover); } else if (element.overriddenScopeList.length || element.overriddenDefaultsLanguageList.length) { - if ((MODIFIED_INDICATOR_USE_INLINE_ONLY && element.overriddenScopeList.length) || - (element.overriddenScopeList.length === 1 && !element.overriddenDefaultsLanguageList.length)) { - // This branch renders some info inline! - // Render inline if we have the flag and there are scope overrides to render, - // or if there is only one scope override to render and no language overrides. + if (element.overriddenScopeList.length === 1 && !element.overriddenDefaultsLanguageList.length) { this.scopeOverridesIndicator.element.style.display = 'inline'; this.scopeOverridesIndicator.element.classList.remove('setting-indicator'); - this.scopeOverridesIndicator.hover.value = undefined; + this.scopeOverridesIndicator.disposables.clear(); // Just show all the text in the label. const prefaceText = element.isConfigured ? @@ -269,10 +307,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { e.stopPropagation(); })); } - } else if (!MODIFIED_INDICATOR_USE_INLINE_ONLY) { - // Even if the check above fails, we want to - // show the text in a custom hover only if - // the feature flag isn't on. + } else { this.scopeOverridesIndicator.element.style.display = 'inline'; this.scopeOverridesIndicator.element.classList.add('setting-indicator'); const scopeOverridesLabelText = element.isConfigured ? @@ -281,53 +316,48 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.scopeOverridesIndicator.label.text = scopeOverridesLabelText; let contentMarkdownString = ''; - let contentFallback = ''; if (element.overriddenScopeList.length) { const prefaceText = element.isConfigured ? localize('alsoModifiedInScopes', "The setting has also been modified in the following scopes:") : localize('modifiedInScopes', "The setting has been modified in the following scopes:"); contentMarkdownString = prefaceText; - contentFallback = prefaceText; for (const scope of element.overriddenScopeList) { const scopeDisplayText = this.getInlineScopeDisplayText(scope); contentMarkdownString += `\n- [${scopeDisplayText}](${encodeURIComponent(scope)} "${getAccessibleScopeDisplayText(scope, this.languageService)}")`; - contentFallback += `\n• ${scopeDisplayText}`; } } if (element.overriddenDefaultsLanguageList.length) { if (contentMarkdownString) { contentMarkdownString += `\n\n`; - contentFallback += `\n\n`; } const prefaceText = localize('hasDefaultOverridesForLanguages', "The following languages have default overrides:"); contentMarkdownString += prefaceText; - contentFallback += prefaceText; for (const language of element.overriddenDefaultsLanguageList) { const scopeDisplayText = this.languageService.getLanguageName(language); contentMarkdownString += `\n- [${scopeDisplayText}](${encodeURIComponent(`default:${language}`)} "${scopeDisplayText}")`; - contentFallback += `\n• ${scopeDisplayText}`; } } - const content: ITooltipMarkdownString = { - markdown: { - value: contentMarkdownString, - isTrusted: false, - supportHtml: false - }, - markdownNotSupportedFallback: contentFallback + const content: IMarkdownString = { + value: contentMarkdownString, + isTrusted: false, + supportHtml: false }; - const options: IUpdatableHoverOptions = { - linkHandler: (url: string) => { - const [scope, language] = decodeURIComponent(url).split(':'); - onDidClickOverrideElement.fire({ - settingKey: element.setting.key, - scope: scope as ScopeString, - language - }); - this.scopeOverridesIndicator.hover.value?.hide(); - } + const showHover = (focus: boolean) => { + return this.hoverService.showHover({ + ...this.defaultHoverOptions, + content, + linkHandler: (url: string) => { + const [scope, language] = decodeURIComponent(url).split(':'); + onDidClickOverrideElement.fire({ + settingKey: element.setting.key, + scope: scope as ScopeString, + language + }); + }, + target: this.scopeOverridesIndicator.element + }, focus); }; - this.scopeOverridesIndicator.hover.value = setupCustomHover(this.hoverDelegate, this.scopeOverridesIndicator.element, content, options); + this.addHoverDisposables(this.scopeOverridesIndicator.disposables, this.scopeOverridesIndicator.element, showHover); } } this.render(); @@ -338,9 +368,19 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { const sourceToDisplay = getDefaultValueSourceToDisplay(element); if (sourceToDisplay !== undefined) { this.defaultOverrideIndicator.element.style.display = 'inline'; + this.defaultOverrideIndicator.disposables.clear(); const defaultOverrideHoverContent = localize('defaultOverriddenDetails', "Default setting value overridden by {0}", sourceToDisplay); - this.defaultOverrideIndicator.hover.value = setupCustomHover(this.hoverDelegate, this.defaultOverrideIndicator.element, defaultOverrideHoverContent); + const showHover = (focus: boolean) => { + return this.hoverService.showHover({ + content: defaultOverrideHoverContent, + target: this.defaultOverrideIndicator.element, + hoverPosition: HoverPosition.BELOW, + showPointer: true, + compact: false + }, focus); + }; + this.addHoverDisposables(this.defaultOverrideIndicator.disposables, this.defaultOverrideIndicator.element, showHover); } this.render(); } diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index fbfc94fa983..0bd27f0ebb5 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -86,4 +86,3 @@ export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace'; export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker'; export const ENABLE_LANGUAGE_FILTER = true; -export const MODIFIED_INDICATOR_USE_INLINE_ONLY = false; From 8aa17c5d86a744fce0461feb578f6dfd6bac0212 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 17 Nov 2022 10:31:14 -0800 Subject: [PATCH 54/67] Flatten input latency telemetry event Follow up from #163184 --- .../browser/inputLatencyContrib.ts | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts b/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts index 4f85f56e7dd..3963cda75fb 100644 --- a/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts +++ b/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts @@ -46,15 +46,51 @@ export class InputLatencyContrib extends Disposable implements IWorkbenchContrib type PerformanceInputLatencyClassification = { owner: 'tyriar'; comment: 'This is a set of samples of the time (in milliseconds) that various events took when typing in the editor'; - keydown: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min, max and average time it took for the keydown event to execute.' }; - input: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min, max and average time it took for the input event to execute.' }; - render: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min, max and average time it took for the render animation frame to execute.' }; - total: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min, max and average input latency.' }; + 'keydown.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min time it took for the keydown event to execute.' }; + 'keydown.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max time it took for the keydown event to execute.' }; + 'keydown.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took for the keydown event to execute.' }; + 'input.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min time it took for the input event to execute.' }; + 'input.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max time it took for the input event to execute.' }; + 'input.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took for the input event to execute.' }; + 'render.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min time it took for the render animation frame to execute.' }; + 'render.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max time it took for the render animation frame to execute.' }; + 'render.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took for the render animation frame to execute.' }; + 'total.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min input latency.' }; + 'total.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max input latency.' }; + 'total.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average input latency.' }; sampleCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The number of samples measured.' }; }; - type PerformanceInputLatencyEvent = inputLatency.IInputLatencyMeasurements; + type PerformanceInputLatencyEvent = { + 'keydown.average': number; + 'keydown.max': number; + 'keydown.min': number; + 'input.average': number; + 'input.max': number; + 'input.min': number; + 'render.average': number; + 'render.max': number; + 'render.min': number; + 'total.average': number; + 'total.max': number; + 'total.min': number; + sampleCount: number; + }; - this._telemetryService.publicLog2('performance.inputLatency', measurements); + this._telemetryService.publicLog2('performance.inputLatency', { + 'keydown.average': measurements.keydown.average, + 'keydown.max': measurements.keydown.max, + 'keydown.min': measurements.keydown.min, + 'input.average': measurements.input.average, + 'input.max': measurements.input.max, + 'input.min': measurements.input.min, + 'render.average': measurements.render.average, + 'render.max': measurements.render.max, + 'render.min': measurements.render.min, + 'total.average': measurements.total.average, + 'total.max': measurements.total.max, + 'total.min': measurements.total.min, + sampleCount: measurements.sampleCount + }); } } From 5865df366ad51b620fd6c34c617b61b7dc42ab70 Mon Sep 17 00:00:00 2001 From: MonadChains Date: Thu, 17 Nov 2022 19:39:24 +0100 Subject: [PATCH 55/67] Implement Audio cues on cell execution completed (#165442) * Implement Audio cues on cell execution completed * Revert refactor and improve behaviour * Add audio cues for notebook execution * remove old setting Co-authored-by: Rob Lourens --- src/vs/platform/audioCues/browser/audioCueService.ts | 12 ++++++++++++ .../audioCues/browser/audioCues.contribution.ts | 8 ++++++++ .../services/notebookExecutionStateServiceImpl.ts | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index a7ca2398964..932c88be3f4 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -249,6 +249,18 @@ export class AudioCue { settingsKey: 'audioCues.terminalBell' }); + public static readonly notebookCellCompleted = AudioCue.register({ + name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), + sound: Sound.taskCompleted, + settingsKey: 'audioCues.notebookCellCompleted' + }); + + public static readonly notebookCellFailed = AudioCue.register({ + name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), + sound: Sound.taskFailed, + settingsKey: 'audioCues.notebookCellFailed' + }); + public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index bcfbc1fae7f..78bf29148c2 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -95,6 +95,14 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in diff review mode"), ...audioCueFeatureBase, }, + 'audioCues.notebookCellCompleted': { + 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase, + }, + 'audioCues.notebookCellFailed': { + 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), + ...audioCueFeatureBase, + }, } }); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index 7cf663d07f6..66eeeae13b9 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -9,6 +9,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -36,6 +37,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotebookService private readonly _notebookService: INotebookService, + @IAudioCueService private readonly _audioCueService: IAudioCueService ) { super(); } @@ -104,8 +106,12 @@ export class NotebookExecutionStateService extends Disposable implements INotebo if (lastRunSuccess !== undefined) { if (lastRunSuccess) { + if (this._executions.size === 0) { + this._audioCueService.playAudioCue(AudioCue.notebookCellCompleted); + } this._clearLastFailedCell(notebookUri); } else { + this._audioCueService.playAudioCue(AudioCue.notebookCellFailed); this._setLastFailedCell(notebookUri, cellHandle); } } From 827c64767efa3fb16537bf4914ebe2de5a6af8fc Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 17 Nov 2022 10:52:09 -0800 Subject: [PATCH 56/67] use correct file path to audio cues (#166618) fix #166617 --- src/vs/platform/audioCues/browser/audioCueService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 932c88be3f4..3508c387929 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -70,7 +70,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.playingSounds.add(sound); const url = FileAccess.asBrowserUri( - `vs/platform/audioCues/common/media/${sound.fileName}` + `vs/platform/audioCues/browser/media/${sound.fileName}` ).toString(); const audio = new Audio(url); audio.volume = this.getVolumeInPercent() / 100; From 061e7540cddc53431ba0bee2d7881e355b05ceb5 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 17 Nov 2022 10:56:52 -0800 Subject: [PATCH 57/67] Restore missing command palette commands (#166621) command Replace in files is gone Fixes #166134 --- .../search/browser/searchActionsNav.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index bc0f99679f8..77a6b79b1f1 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -403,8 +403,8 @@ registerAction2(class FocusNextSearchResultAction extends Action2 { primary: KeyCode.F4, weight: KeybindingWeight.WorkbenchContrib, }], - category: category.value, - + category: category, + f1: true, precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), }); } @@ -419,15 +419,15 @@ registerAction2(class FocusPreviousSearchResultAction extends Action2 { super({ id: Constants.FocusPreviousSearchResultActionId, title: { - value: nls.localize('FocusPreviousSearchResult.label', 'Search: Focus Previous Search Result'), - original: 'Search: Focus Previous Search Result' + value: nls.localize('FocusPreviousSearchResult.label', 'Focus Previous Search Result'), + original: 'Focus Previous Search Result' }, keybinding: [{ primary: KeyMod.Shift | KeyCode.F4, weight: KeybindingWeight.WorkbenchContrib, }], - category: category.value, - + category: category, + f1: true, precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), }); } @@ -442,14 +442,15 @@ registerAction2(class ReplaceInFilesAction extends Action2 { super({ id: Constants.ReplaceInFilesActionId, title: { - value: nls.localize('replaceInFiles', 'Search: Replace in Files'), - original: 'Search: Replace in Files' + value: nls.localize('replaceInFiles', 'Replace in Files'), + original: 'Replace in Files' }, keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyH, weight: KeybindingWeight.WorkbenchContrib, }], - category: category.value, + category: category, + f1: true, menu: [{ id: MenuId.MenubarEditMenu, group: '4_find_global', From 215532049043180c2a732161640af703590e8eff Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 17 Nov 2022 14:01:17 -0500 Subject: [PATCH 58/67] Use composition instead --- .../browser/inputLatencyContrib.ts | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts b/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts index 3963cda75fb..32f3bbe3273 100644 --- a/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts +++ b/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts @@ -43,53 +43,31 @@ export class InputLatencyContrib extends Disposable implements IWorkbenchContrib return; } + type InputLatencyStatisticFragment = { + owner: 'tyriar'; + comment: 'Represents a set of statistics collected about input latencies'; + average: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took to execute.'; isMeasurement: true }; + max: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The maximum time it took to execute.'; isMeasurement: true }; + min: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The minimum time it took to execute.'; isMeasurement: true }; + }; + type PerformanceInputLatencyClassification = { owner: 'tyriar'; comment: 'This is a set of samples of the time (in milliseconds) that various events took when typing in the editor'; - 'keydown.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min time it took for the keydown event to execute.' }; - 'keydown.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max time it took for the keydown event to execute.' }; - 'keydown.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took for the keydown event to execute.' }; - 'input.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min time it took for the input event to execute.' }; - 'input.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max time it took for the input event to execute.' }; - 'input.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took for the input event to execute.' }; - 'render.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min time it took for the render animation frame to execute.' }; - 'render.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max time it took for the render animation frame to execute.' }; - 'render.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average time it took for the render animation frame to execute.' }; - 'total.average': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The min input latency.' }; - 'total.max': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The max input latency.' }; - 'total.min': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The average input latency.' }; + keydown: InputLatencyStatisticFragment; + input: InputLatencyStatisticFragment; + render: InputLatencyStatisticFragment; + total: InputLatencyStatisticFragment; sampleCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The number of samples measured.' }; }; - type PerformanceInputLatencyEvent = { - 'keydown.average': number; - 'keydown.max': number; - 'keydown.min': number; - 'input.average': number; - 'input.max': number; - 'input.min': number; - 'render.average': number; - 'render.max': number; - 'render.min': number; - 'total.average': number; - 'total.max': number; - 'total.min': number; - sampleCount: number; - }; + type PerformanceInputLatencyEvent = inputLatency.IInputLatencyMeasurements; this._telemetryService.publicLog2('performance.inputLatency', { - 'keydown.average': measurements.keydown.average, - 'keydown.max': measurements.keydown.max, - 'keydown.min': measurements.keydown.min, - 'input.average': measurements.input.average, - 'input.max': measurements.input.max, - 'input.min': measurements.input.min, - 'render.average': measurements.render.average, - 'render.max': measurements.render.max, - 'render.min': measurements.render.min, - 'total.average': measurements.total.average, - 'total.max': measurements.total.max, - 'total.min': measurements.total.min, + keydown: measurements.keydown, + input: measurements.input, + render: measurements.render, + total: measurements.total, sampleCount: measurements.sampleCount }); } From 9dd6dc2874bce63fd4f0106686a2406d19b9954d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 17 Nov 2022 11:16:30 -0800 Subject: [PATCH 59/67] cli: check glibc version more correctly on gnu (#166622) Fixes https://github.com/microsoft/vscode-remote-release/issues/7495 --- cli/src/util/prereqs.rs | 83 ++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index 0070858ddeb..46bb0dc6dbf 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -17,6 +17,7 @@ use super::errors::AnyError; lazy_static! { static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap(); static ref LDD_VERSION_RE: BinRegex = BinRegex::new(r"^ldd.*(.+)\.(.+)\s").unwrap(); + static ref GENERIC_VERSION_RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)$").unwrap(); static ref LIBSTD_CXX_VERSION_RE: BinRegex = BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap(); static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 18); @@ -116,13 +117,23 @@ async fn check_musl_interpreter() -> Result<(), String> { #[allow(dead_code)] async fn check_glibc_version() -> Result<(), String> { - let ldd_version = capture_command("ldd", ["--version"]) - .await - .ok() - .and_then(|o| extract_ldd_version(&o.stdout)); + #[cfg(target_env = "gnu")] + let version = { + let v = unsafe { libc::gnu_get_libc_version() }; + let v = unsafe { std::ffi::CStr::from_ptr(v) }; + let v = v.to_str().unwrap(); + extract_generic_version(v) + }; + #[cfg(not(target_env = "gnu"))] + let version = { + capture_command("ldd", ["--version"]) + .await + .ok() + .and_then(|o| extract_ldd_version(&o.stdout)) + }; - if let Some(v) = ldd_version { - return if v.gte(&MIN_LDD_VERSION) { + if let Some(v) = version { + return if v >= *MIN_LDD_VERSION { Ok(()) } else { Err(format!( @@ -181,7 +192,7 @@ fn check_for_sufficient_glibcxx_versions(contents: Vec) -> Result<(), String }) .collect(); - if !all_versions.iter().any(|v| MIN_CXX_VERSION.gte(v)) { + if !all_versions.iter().any(|v| &*MIN_CXX_VERSION >= v) { return Err(format!( "find GLIBCXX >= 3.4.18 (but found {} instead) for GNU environments", all_versions @@ -195,6 +206,7 @@ fn check_for_sufficient_glibcxx_versions(contents: Vec) -> Result<(), String Ok(()) } +#[allow(dead_code)] fn extract_ldd_version(output: &[u8]) -> Option { LDD_VERSION_RE.captures(output).map(|m| SimpleSemver { major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())), @@ -203,6 +215,15 @@ fn extract_ldd_version(output: &[u8]) -> Option { }) } +#[allow(dead_code)] +fn extract_generic_version(output: &str) -> Option { + GENERIC_VERSION_RE.captures(output).map(|m| SimpleSemver { + major: m.get(1).map_or(0, |s| s.as_str().parse().unwrap()), + minor: m.get(2).map_or(0, |s| s.as_str().parse().unwrap()), + patch: 0, + }) +} + fn extract_libstd_from_ldconfig(output: &[u8]) -> Option { String::from_utf8_lossy(output) .lines() @@ -215,13 +236,35 @@ fn u32_from_bytes(b: &[u8]) -> u32 { String::from_utf8_lossy(b).parse::().unwrap_or(0) } -#[derive(Debug, PartialEq)] +#[derive(Debug, Default, PartialEq, Eq)] struct SimpleSemver { major: u32, minor: u32, patch: u32, } +impl PartialOrd for SimpleSemver { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SimpleSemver { + fn cmp(&self, other: &Self) -> Ordering { + let major = self.major.cmp(&other.major); + if major != Ordering::Equal { + return major; + } + + let minor = self.minor.cmp(&other.minor); + if minor != Ordering::Equal { + return minor; + } + + self.patch.cmp(&other.patch) + } +} + impl From<&SimpleSemver> for String { fn from(s: &SimpleSemver) -> Self { format!("v{}.{}.{}", s.major, s.minor, s.patch) @@ -243,18 +286,6 @@ impl SimpleSemver { patch, } } - - fn gte(&self, other: &SimpleSemver) -> bool { - match self.major.cmp(&other.major) { - Ordering::Greater => true, - Ordering::Less => false, - Ordering::Equal => match self.minor.cmp(&other.minor) { - Ordering::Greater => true, - Ordering::Less => false, - Ordering::Equal => self.patch >= other.patch, - }, - } - } } #[cfg(test)] @@ -284,13 +315,13 @@ mod tests { #[test] fn test_gte() { - assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 3))); - assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(0, 10, 10))); - assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 1, 10))); + assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(1, 2, 3)); + assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(0, 10, 10)); + assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(1, 1, 10)); - assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 10))); - assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 3, 1))); - assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(2, 2, 1))); + assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(1, 2, 10)); + assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(1, 3, 1)); + assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(2, 2, 1)); } #[test] From 184ba7b0f4c681ce957e3d3511d4a1a0613dedb2 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:44:38 -0800 Subject: [PATCH 60/67] Restyle Edit in settings.json link (#166629) * Re-style Edit in settings.json link Fixes #166625 Also refactors the code a bit. * Adjust opacity --- .../browser/media/settingsEditor2.css | 21 ++++---- .../settingsEditorSettingIndicators.ts | 53 +++++++++---------- .../preferences/browser/settingsTree.ts | 35 +++++------- 3 files changed, 49 insertions(+), 60 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index de3754709ab..61d9d5c5f95 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -484,12 +484,18 @@ margin: 0px; } +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button { + opacity: 0.9; +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a > code { color: var(--vscode-textLink-foreground); } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:focus { +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:focus, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button:focus { outline: 1px solid -webkit-focus-ring-color; outline-offset: -1px; text-decoration: underline; @@ -498,12 +504,15 @@ .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:hover, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:active, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button:hover, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button:active, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:hover > code, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:active > code { color: var(--vscode-textLink-activeForeground); } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:hover { +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:hover, +.settings-editor > .settings-body .settings-tree-container .edit-in-settings-button:hover { cursor: pointer; text-decoration: underline; } @@ -570,14 +579,6 @@ width: 320px; } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button, -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button:hover, -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button:active { - text-align: left; - text-decoration: underline; - padding-left: 0px; -} - .settings-editor > .settings-body .settings-tree-container .setting-item-contents .monaco-select-box { width: initial; font: inherit; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 9c5daa698b7..761bfb04414 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -26,7 +26,7 @@ import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/service const $ = DOM.$; -type ScopeString = 'workspace' | 'user' | 'remote'; +type ScopeString = 'workspace' | 'user' | 'remote' | 'default'; export interface ISettingOverrideClickEvent { scope: ScopeString; @@ -44,14 +44,15 @@ interface SettingIndicator { * Renders the indicators next to a setting, such as "Also Modified In". */ export class SettingsTreeIndicatorsLabel implements IDisposable { - private indicatorsContainerElement: HTMLElement; + private readonly indicatorsContainerElement: HTMLElement; - private workspaceTrustIndicator: SettingIndicator; - private scopeOverridesIndicator: SettingIndicator; - private syncIgnoredIndicator: SettingIndicator; - private defaultOverrideIndicator: SettingIndicator; + private readonly workspaceTrustIndicator: SettingIndicator; + private readonly scopeOverridesIndicator: SettingIndicator; + private readonly syncIgnoredIndicator: SettingIndicator; + private readonly defaultOverrideIndicator: SettingIndicator; + private readonly allIndicators: SettingIndicator[]; - private profilesEnabled: boolean; + private readonly profilesEnabled: boolean; constructor( container: HTMLElement, @@ -70,6 +71,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.scopeOverridesIndicator = this.createScopeOverridesIndicator(); this.syncIgnoredIndicator = this.createSyncIgnoredIndicator(); this.defaultOverrideIndicator = this.createDefaultOverrideIndicator(); + this.allIndicators = [this.workspaceTrustIndicator, this.scopeOverridesIndicator, this.syncIgnoredIndicator, this.defaultOverrideIndicator]; } private defaultHoverOptions: Partial = { @@ -186,7 +188,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { } private render() { - const indicatorsToShow = [this.workspaceTrustIndicator, this.scopeOverridesIndicator, this.syncIgnoredIndicator, this.defaultOverrideIndicator].filter(indicator => { + const indicatorsToShow = this.allIndicators.filter(indicator => { return indicator.element.style.display !== 'none'; }); @@ -279,34 +281,31 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.addHoverDisposables(this.scopeOverridesIndicator.disposables, this.scopeOverridesIndicator.element, showHover); } else if (element.overriddenScopeList.length || element.overriddenDefaultsLanguageList.length) { if (element.overriddenScopeList.length === 1 && !element.overriddenDefaultsLanguageList.length) { + // We can inline the override and show all the text in the label + // so that users don't have to wait for the hover to load + // just to click into the one override there is. this.scopeOverridesIndicator.element.style.display = 'inline'; this.scopeOverridesIndicator.element.classList.remove('setting-indicator'); this.scopeOverridesIndicator.disposables.clear(); - // Just show all the text in the label. const prefaceText = element.isConfigured ? localize('alsoConfiguredIn', "Also modified in") : localize('configuredIn', "Modified in"); this.scopeOverridesIndicator.label.text = `${prefaceText} `; - for (let i = 0; i < element.overriddenScopeList.length; i++) { - const overriddenScope = element.overriddenScopeList[i]; - const view = DOM.append(this.scopeOverridesIndicator.element, $('a.modified-scope', undefined, this.getInlineScopeDisplayText(overriddenScope))); - if (i !== element.overriddenScopeList.length - 1) { - DOM.append(this.scopeOverridesIndicator.element, $('span.comma', undefined, ', ')); - } - elementDisposables.add( - DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { - const [scope, language] = overriddenScope.split(':'); - onDidClickOverrideElement.fire({ - settingKey: element.setting.key, - scope: scope as ScopeString, - language - }); - e.preventDefault(); - e.stopPropagation(); - })); - } + const overriddenScope = element.overriddenScopeList[0]; + const view = DOM.append(this.scopeOverridesIndicator.element, $('a.modified-scope', undefined, this.getInlineScopeDisplayText(overriddenScope))); + elementDisposables.add( + DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { + const [scope, language] = overriddenScope.split(':'); + onDidClickOverrideElement.fire({ + settingKey: element.setting.key, + scope: scope as ScopeString, + language + }); + e.preventDefault(); + e.stopPropagation(); + })); } else { this.scopeOverridesIndicator.element.style.display = 'inline'; this.scopeOverridesIndicator.element.classList.add('setting-indicator'); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index a84832bc24e..6f37adb815b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -18,8 +18,6 @@ import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction, Separator } from 'vs/base/common/actions'; -import * as arrays from 'vs/base/common/arrays'; -import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -64,7 +62,7 @@ import { getIndicatorsLabelAriaLabel, ISettingOverrideClickEvent, SettingsTreeIn import { ILanguageService } from 'vs/editor/common/languages/language'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { defaultButtonStyles, getButtonStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; const $ = DOM.$; @@ -97,7 +95,7 @@ function areAllPropertiesDefined(properties: string[], itemsToDisplay: IObjectDa function getEnumOptionsFromSchema(schema: IJSONSchema): IObjectEnumOption[] { if (schema.anyOf) { - return arrays.flatten(schema.anyOf.map(getEnumOptionsFromSchema)); + return schema.anyOf.map(getEnumOptionsFromSchema).flat(); } const enumDescriptions = schema.enumDescriptions ?? []; @@ -425,8 +423,7 @@ export async function createTocTreeForExtensionSettings(extensionService: IExten extGroupTree.get(extensionId)!.children!.push(childEntry); }; const processGroupEntry = async (group: ISettingsGroup) => { - const flatSettings = arrays.flatten( - group.sections.map(section => section.settings)); + const flatSettings = group.sections.map(section => section.settings).flat(); const extensionId = group.extensionInfo!.id; const extension = await extensionService.getExtension(extensionId); @@ -509,7 +506,7 @@ function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set getMatchingSettings(allSettings, pattern, logService))); + settings = tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern, logService)).flat(); } if (!children && !settings) { @@ -619,7 +616,7 @@ interface ISettingEnumItemTemplate extends ISettingItemTemplate { } interface ISettingComplexItemTemplate extends ISettingItemTemplate { - button: Button; + button: HTMLElement; validationErrorMessageElement: HTMLElement; } @@ -1051,17 +1048,9 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I renderTemplate(container: HTMLElement): ISettingComplexItemTemplate { const common = this.renderCommonTemplate(null, container, 'complex'); - const openSettingsButton = new Button(common.controlElement, { - title: true, ...getButtonStyles({ - buttonBackground: Color.transparent.toString(), - buttonHoverBackground: Color.transparent.toString(), - buttonForeground: 'foreground' - }) - }); - common.toDispose.add(openSettingsButton); - - openSettingsButton.element.classList.add('edit-in-settings-button'); - openSettingsButton.element.classList.add(AbstractSettingRenderer.CONTROL_CLASS); + const openSettingsButton = DOM.append(common.controlElement, $('a.edit-in-settings-button')); + openSettingsButton.classList.add(AbstractSettingRenderer.CONTROL_CLASS); + openSettingsButton.role = 'button'; const validationErrorMessageElement = $('.setting-item-validation-message'); common.containerElement.appendChild(validationErrorMessageElement); @@ -1085,11 +1074,11 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I const plainKey = getLanguageTagSettingPlainKey(dataElement.setting.key); const editLanguageSettingLabel = localize('editLanguageSettingLabel', "Edit settings for {0}", plainKey); const isLanguageTagSetting = dataElement.setting.isLanguageTagSetting; - template.button.label = isLanguageTagSetting + template.button.textContent = isLanguageTagSetting ? editLanguageSettingLabel : SettingComplexRenderer.EDIT_IN_JSON_LABEL; - template.elementDisposables.add(template.button.onDidClick(() => { + template.elementDisposables.add(DOM.addDisposableListener(template.button, DOM.EventType.CLICK, () => { if (isLanguageTagSetting) { this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}${plainKey}`); } else { @@ -1100,9 +1089,9 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I this.renderValidations(dataElement, template); if (isLanguageTagSetting) { - template.button.element.setAttribute('aria-label', editLanguageSettingLabel); + template.button.setAttribute('aria-label', editLanguageSettingLabel); } else { - template.button.element.setAttribute('aria-label', `${SettingComplexRenderer.EDIT_IN_JSON_LABEL}: ${dataElement.setting.key}`); + template.button.setAttribute('aria-label', `${SettingComplexRenderer.EDIT_IN_JSON_LABEL}: ${dataElement.setting.key}`); } } From 896c32bb50781c723064542bc8c22899d19ca3ba Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:50:46 -0800 Subject: [PATCH 61/67] Enable WCO on Insiders and Exploration (#166386) Ref #161218 --- src/vs/platform/window/common/window.ts | 7 +++++-- src/vs/platform/windows/electron-main/windowImpl.ts | 4 ++-- src/vs/workbench/browser/parts/titlebar/titlebarPart.ts | 4 +++- src/vs/workbench/electron-sandbox/desktop.contribution.ts | 2 +- .../electron-sandbox/parts/titlebar/titlebarPart.ts | 8 +++++--- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 3a7393455ee..408e9f667da 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -14,6 +14,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { FileType } from 'vs/platform/files/common/files'; import { LogLevel } from 'vs/platform/log/common/log'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IAnyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -163,7 +164,7 @@ export function getTitleBarStyle(configurationService: IConfigurationService): ' return isLinux ? 'native' : 'custom'; // default to custom on all macOS and Windows } -export function useWindowControlsOverlay(configurationService: IConfigurationService): boolean { +export function useWindowControlsOverlay(configurationService: IConfigurationService, productService: IProductService): boolean { if (!isWindows || isWeb) { return false; // only supported on a desktop Windows instance } @@ -177,7 +178,9 @@ export function useWindowControlsOverlay(configurationService: IConfigurationSer return configuredUseWindowControlsOverlay; } - return false; // disable by default + // Default to true for Insider and Exploration to match with + // app.getPreferredSystemLanguages() only being available on those builds. + return productService.quality === 'insider' || productService.quality === 'exploration'; } export interface IPath extends IPathData { diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 8efb68d9130..1f37bc6b114 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -274,7 +274,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { options.frame = false; } - if (useWindowControlsOverlay(this.configurationService)) { + if (useWindowControlsOverlay(this.configurationService, this.productService)) { // This logic will not perfectly guess the right colors // to use on initialization, but prefer to keep things @@ -305,7 +305,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Update the window controls immediately based on cached values - if (useCustomTitleStyle && ((isWindows && useWindowControlsOverlay(this.configurationService)) || isMacintosh)) { + if (useCustomTitleStyle && ((isWindows && useWindowControlsOverlay(this.configurationService, this.productService)) || isMacintosh)) { const cachedWindowControlHeight = this.stateMainService.getItem((CodeWindow.windowControlHeightStateStorageKey)); if (cachedWindowControlHeight) { this.updateWindowControls({ height: cachedWindowControlHeight }); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 9a1786e1e08..faa3ba17b07 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -36,6 +36,7 @@ import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IProductService } from 'vs/platform/product/common/productService'; export class TitlebarPart extends Part implements ITitleService { @@ -89,12 +90,13 @@ export class TitlebarPart extends Part implements ITitleService { @IConfigurationService protected readonly configurationService: IConfigurationService, @IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService, @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IProductService protected readonly productService: IProductService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @IHoverService hoverService: IHoverService, + @IHoverService hoverService: IHoverService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.windowTitle = this._register(instantiationService.createInstance(WindowTitle)); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index d2a4a62970b..125e0a8a3c0 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -207,7 +207,7 @@ import product from 'vs/platform/product/common/product'; }, 'window.experimental.windowControlsOverlay.enabled': { 'type': 'boolean', - 'default': false, + 'default': product.quality === 'insider' || product.quality === 'exploration', // switch back to true when app.getLocale() isn't used anymore. 'scope': ConfigurationScope.APPLICATION, 'description': localize('windowControlsOverlay', "Use window controls provided by the platform instead of our HTML-based window controls. Changes require a full restart to apply."), 'included': isWindows diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 35899ccaa86..e4144ba1fec 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -22,6 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Codicon } from 'vs/base/common/codicons'; import { NativeMenubarControl } from 'vs/workbench/electron-sandbox/parts/titlebar/menubarControl'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IProductService } from 'vs/platform/product/common/productService'; export class TitlebarPart extends BrowserTitleBarPart { private maxRestoreControl: HTMLElement | undefined; @@ -58,6 +59,7 @@ export class TitlebarPart extends BrowserTitleBarPart { @IConfigurationService configurationService: IConfigurationService, @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IInstantiationService instantiationService: IInstantiationService, + @IProductService productService: IProductService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -66,7 +68,7 @@ export class TitlebarPart extends BrowserTitleBarPart { @INativeHostService private readonly nativeHostService: INativeHostService, @IHoverService hoverService: IHoverService, ) { - super(contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService); + super(contextMenuService, configurationService, environmentService, instantiationService, productService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService); this.environmentService = environmentService; } @@ -216,7 +218,7 @@ export class TitlebarPart extends BrowserTitleBarPart { super.updateStyles(); // WCO styles only supported on Windows currently - if (useWindowControlsOverlay(this.configurationService)) { + if (useWindowControlsOverlay(this.configurationService, this.productService)) { if (!this.cachedWindowControlStyles || this.cachedWindowControlStyles.bgColor !== this.element.style.backgroundColor || this.cachedWindowControlStyles.fgColor !== this.element.style.color) { @@ -228,7 +230,7 @@ export class TitlebarPart extends BrowserTitleBarPart { override layout(width: number, height: number): void { super.layout(width, height); - if (useWindowControlsOverlay(this.configurationService) || + if (useWindowControlsOverlay(this.configurationService, this.productService) || (isMacintosh && isNative && getTitleBarStyle(this.configurationService) === 'custom')) { // When the user goes into full screen mode, the height of the title bar becomes 0. // Instead, set it back to the default titlebar height for Catalina users From 8f2c8e36210a751f6e6ff196c0780336ff386fdd Mon Sep 17 00:00:00 2001 From: Nisarg Jhaveri <6381721+nisargjhaveri@users.noreply.github.com> Date: Fri, 18 Nov 2022 02:09:40 +0530 Subject: [PATCH 62/67] Allow different exception breakpoints from multiple debuggers to be shown at once (#158355) * Update exception breakpoints based on active debug session - Keep track of all exception breakpoints, instead of the ones from last started session - Update breakpoints view when focused debugger changes to show the correct set of exception breakpoints - Store all exception breakpoints in model for restoring enablement later - Only send the correct set of breakpoints for each debug adapter when updated * Show last session's exception breakpoints even when the session is not active * Add more tests for exception breakpoints in multiple sessions * tsdoc commetns Co-authored-by: Rob Lourens --- .../contrib/debug/browser/breakpointsView.ts | 14 ++-- .../contrib/debug/browser/debugService.ts | 20 +++-- .../contrib/debug/browser/debugSession.ts | 2 +- .../workbench/contrib/debug/common/debug.ts | 13 +++- .../contrib/debug/common/debugModel.ts | 74 ++++++++++++++++--- .../contrib/debug/common/debugStorage.ts | 2 +- .../debug/test/browser/breakpoints.test.ts | 67 +++++++++++++++-- .../contrib/debug/test/common/mockDebug.ts | 2 +- 8 files changed, 161 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 2af2ed66341..57702c0b841 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -63,8 +63,8 @@ function createCheckbox(disposables: IDisposable[]): HTMLInputElement { } const MAX_VISIBLE_BREAKPOINTS = 9; -export function getExpandedBodySize(model: IDebugModel, countLimit: number): number { - const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length + model.getInstructionBreakpoints().length; +export function getExpandedBodySize(model: IDebugModel, sessionId: string | undefined, countLimit: number): number { + const length = model.getBreakpoints().length + model.getExceptionBreakpointsForSession(sessionId).length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length + model.getInstructionBreakpoints().length; return Math.min(countLimit, length) * 22; } type BreakpointItem = IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IExceptionBreakpoint | IInstructionBreakpoint; @@ -117,7 +117,7 @@ export class BreakpointsView extends ViewPane { this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService); this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); - this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateBreakpointsHint())); + this._register(this.debugService.getViewModel().onDidFocusSession(() => this.onBreakpointsChange())); this._register(this.debugService.onDidChangeState(() => this.onStateChange())); this.hintDelayer = this._register(new RunOnceScheduler(() => this.updateBreakpointsHint(true), 4000)); } @@ -273,8 +273,9 @@ export class BreakpointsView extends ViewPane { const containerModel = this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!; // Adjust expanded body size - this.minimumBodySize = this.orientation === Orientation.VERTICAL ? getExpandedBodySize(this.debugService.getModel(), MAX_VISIBLE_BREAKPOINTS) : 170; - this.maximumBodySize = this.orientation === Orientation.VERTICAL && containerModel.visibleViewDescriptors.length > 1 ? getExpandedBodySize(this.debugService.getModel(), Number.POSITIVE_INFINITY) : Number.POSITIVE_INFINITY; + const sessionId = this.debugService.getViewModel().focusedSession?.getId(); + this.minimumBodySize = this.orientation === Orientation.VERTICAL ? getExpandedBodySize(this.debugService.getModel(), sessionId, MAX_VISIBLE_BREAKPOINTS) : 170; + this.maximumBodySize = this.orientation === Orientation.VERTICAL && containerModel.visibleViewDescriptors.length > 1 ? getExpandedBodySize(this.debugService.getModel(), sessionId, Number.POSITIVE_INFINITY) : Number.POSITIVE_INFINITY; } private updateBreakpointsHint(delayed = false): void { @@ -363,7 +364,8 @@ export class BreakpointsView extends ViewPane { private get elements(): BreakpointItem[] { const model = this.debugService.getModel(); - const elements = (>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints()).concat(model.getInstructionBreakpoints()); + const sessionId = this.debugService.getViewModel().focusedSession?.getId(); + const elements = (>model.getExceptionBreakpointsForSession(sessionId)).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints()).concat(model.getInstructionBreakpoints()); return elements as BreakpointItem[]; } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 99327f8edd6..8dd9418a7ad 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -152,8 +152,12 @@ export class DebugService implements IDebugService { this.disposables.add(this.viewModel.onDidFocusStackFrame(() => { this.onStateChange(); })); - this.disposables.add(this.viewModel.onDidFocusSession(() => { + this.disposables.add(this.viewModel.onDidFocusSession((session: IDebugSession | undefined) => { this.onStateChange(); + + if (session) { + this.setExceptionBreakpointFallbackSession(session.getId()); + } })); this.disposables.add(Event.any(this.adapterManager.onDidRegisterDebugger, this.configurationManager.onDidSelectConfiguration)(() => { const debugUxValue = (this.state !== State.Inactive || (this.configurationManager.getAllConfigurations().length > 0 && this.adapterManager.hasEnabledDebuggers())) ? 'default' : 'simple'; @@ -715,6 +719,8 @@ export class DebugService implements IDebugService { } } } + + this.model.removeExceptionBreakpointsForSession(session.getId()); })); } @@ -1046,8 +1052,13 @@ export class DebugService implements IDebugService { await this.sendInstructionBreakpoints(); } - setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { - this.model.setExceptionBreakpoints(data); + setExceptionBreakpointFallbackSession(sessionId: string) { + this.model.setExceptionBreakpointFallbackSession(sessionId); + this.debugStorage.storeBreakpoints(this.model); + } + + setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { + this.model.setExceptionBreakpointsForSession(session.getId(), data); this.debugStorage.storeBreakpoints(this.model); } @@ -1121,9 +1132,8 @@ export class DebugService implements IDebugService { } private sendExceptionBreakpoints(session?: IDebugSession): Promise { - const enabledExceptionBps = this.model.getExceptionBreakpoints().filter(exb => exb.enabled); - return sendToOneOrAllSessions(this.model, session, async s => { + const enabledExceptionBps = this.model.getExceptionBreakpointsForSession(s.getId()).filter(exb => exb.enabled); if (s.capabilities.supportsConfigurationDoneRequest && (!s.capabilities.exceptionBreakpointFilters || s.capabilities.exceptionBreakpointFilters.length === 0)) { // Only call `setExceptionBreakpoints` as specified in dap protocol #90001 return; diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 6296138005b..4cd3e34e260 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -312,7 +312,7 @@ export class DebugSession implements IDebugSession { this.initialized = true; this._onDidChangeState.fire(); - this.debugService.setExceptionBreakpoints((this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); + this.debugService.setExceptionBreakpointsForSession(this, (this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); } catch (err) { this.initialized = true; this._onDidChangeState.fire(); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 36f868fdfe6..53cae1e8955 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -600,7 +600,18 @@ export interface IDebugModel extends ITreeElement { areBreakpointsActivated(): boolean; getFunctionBreakpoints(): ReadonlyArray; getDataBreakpoints(): ReadonlyArray; + + /** + * Returns list of all exception breakpoints. + */ getExceptionBreakpoints(): ReadonlyArray; + + /** + * Returns list of exception breakpoints for the given session + * @param sessionId Session id. If falsy, returns the breakpoints from the last set fallback session. + */ + getExceptionBreakpointsForSession(sessionId?: string): ReadonlyArray; + getInstructionBreakpoints(): ReadonlyArray; getWatchExpressions(): ReadonlyArray; @@ -1054,7 +1065,7 @@ export interface IDebugService { setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; - setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void; + setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void; /** * Sends all breakpoints to the passed session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 1f50eb36368..812167ff8db 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -1056,6 +1056,8 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint { + private supportedSessions: Set = new Set(); + constructor( public readonly filter: string, public readonly label: string, @@ -1063,7 +1065,8 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre public readonly supportsCondition: boolean, condition: string | undefined, public readonly description: string | undefined, - public readonly conditionDescription: string | undefined + public readonly conditionDescription: string | undefined, + private fallback: boolean = false ) { super(enabled, undefined, condition, undefined, generateUuid()); } @@ -1075,14 +1078,44 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre result.enabled = this.enabled; result.supportsCondition = this.supportsCondition; result.condition = this.condition; + result.fallback = this.fallback; return result; } + setSupportedSession(sessionId: string, supported: boolean): void { + if (supported) { + this.supportedSessions.add(sessionId); + } + else { + this.supportedSessions.delete(sessionId); + } + } + + /** + * Used to specify which breakpoints to show when no session is specified. + * Useful when no session is active and we want to show the exception breakpoints from the last session. + */ + setFallback(isFallback: boolean) { + this.fallback = isFallback; + } + get supported(): boolean { return true; } + /** + * Checks if the breakpoint is applicable for the specified session. + * If sessionId is undefined, returns true if this breakpoint is a fallback breakpoint. + */ + isSupportedSession(sessionId?: string): boolean { + return sessionId ? this.supportedSessions.has(sessionId) : this.fallback; + } + + matches(filter: DebugProtocol.ExceptionBreakpointsFilter) { + return this.filter === filter.filter && this.label === filter.label && this.supportsCondition === !!filter.supportsCondition && this.conditionDescription === filter.conditionDescription && this.description === filter.description; + } + override toString(): string { return this.label; } @@ -1340,26 +1373,45 @@ export class DebugModel implements IDebugModel { return this.exceptionBreakpoints; } + getExceptionBreakpointsForSession(sessionId?: string): IExceptionBreakpoint[] { + return this.exceptionBreakpoints.filter(ebp => ebp.isSupportedSession(sessionId)); + } + getInstructionBreakpoints(): IInstructionBreakpoint[] { return this.instructionBreakpoints; } - setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { + setExceptionBreakpointsForSession(sessionId: string, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { if (data) { - if (this.exceptionBreakpoints.length === data.length && this.exceptionBreakpoints.every((exbp, i) => - exbp.filter === data[i].filter && exbp.label === data[i].label && exbp.supportsCondition === data[i].supportsCondition && exbp.conditionDescription === data[i].conditionDescription && exbp.description === data[i].description)) { - // No change - return; - } + let didChangeBreakpoints = false; + data.forEach(d => { + let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop(); - this.exceptionBreakpoints = data.map(d => { - const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop(); - return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : !!d.default, !!d.supportsCondition, ebp?.condition, d.description, d.conditionDescription); + if (!ebp) { + didChangeBreakpoints = true; + ebp = new ExceptionBreakpoint(d.filter, d.label, !!d.default, !!d.supportsCondition, undefined /* condition */, d.description, d.conditionDescription); + this.exceptionBreakpoints.push(ebp); + } + + ebp.setSupportedSession(sessionId, true); }); - this._onDidChangeBreakpoints.fire(undefined); + + if (didChangeBreakpoints) { + this._onDidChangeBreakpoints.fire(undefined); + } } } + removeExceptionBreakpointsForSession(sessionId: string): void { + this.exceptionBreakpoints.forEach(ebp => ebp.setSupportedSession(sessionId, false)); + } + + // Set last focused session as fallback session. + // This is done to keep track of the exception breakpoints to show when no session is active. + setExceptionBreakpointFallbackSession(sessionId: string): void { + this.exceptionBreakpoints.forEach(ebp => ebp.setFallback(ebp.isSupportedSession(sessionId))); + } + setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void { (exceptionBreakpoint as ExceptionBreakpoint).condition = condition; this._onDidChangeBreakpoints.fire(undefined); diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index 53ba25115bd..639fc19078b 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -59,7 +59,7 @@ export class DebugStorage { let result: ExceptionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { - return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition, exBreakpoint.description, exBreakpoint.conditionDescription); + return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition, exBreakpoint.description, exBreakpoint.conditionDescription, !!exBreakpoint.fallback); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index dc1df27c873..c3d4af019fa 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -100,10 +100,10 @@ suite('Debug - Breakpoints', () => { const modelUri1 = uri.file('/myfolder/my file first.js'); const modelUri2 = uri.file('/secondfolder/second/second file.js'); addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]); - assert.strictEqual(getExpandedBodySize(model, 9), 44); + assert.strictEqual(getExpandedBodySize(model, undefined, 9), 44); addBreakpointsAndCheckEvents(model, modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]); - assert.strictEqual(getExpandedBodySize(model, 9), 110); + assert.strictEqual(getExpandedBodySize(model, undefined, 9), 110); assert.strictEqual(model.getBreakpoints().length, 5); assert.strictEqual(model.getBreakpoints({ uri: modelUri1 }).length, 2); @@ -137,7 +137,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(bp.enabled, true); model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 })); - assert.strictEqual(getExpandedBodySize(model, 9), 66); + assert.strictEqual(getExpandedBodySize(model, undefined, 9), 66); assert.strictEqual(model.getBreakpoints().length, 3); }); @@ -213,22 +213,75 @@ suite('Debug - Breakpoints', () => { test('exception breakpoints', () => { let eventCount = 0; model.onDidChangeBreakpoints(() => eventCount++); - model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]); + model.setExceptionBreakpointsForSession("session-id-1", [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]); assert.strictEqual(eventCount, 1); - let exceptionBreakpoints = model.getExceptionBreakpoints(); + let exceptionBreakpoints = model.getExceptionBreakpointsForSession("session-id-1"); assert.strictEqual(exceptionBreakpoints.length, 1); assert.strictEqual(exceptionBreakpoints[0].filter, 'uncaught'); assert.strictEqual(exceptionBreakpoints[0].enabled, true); - model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]); + model.setExceptionBreakpointsForSession("session-id-2", [{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]); assert.strictEqual(eventCount, 2); - exceptionBreakpoints = model.getExceptionBreakpoints(); + exceptionBreakpoints = model.getExceptionBreakpointsForSession("session-id-2"); assert.strictEqual(exceptionBreakpoints.length, 2); assert.strictEqual(exceptionBreakpoints[0].filter, 'uncaught'); assert.strictEqual(exceptionBreakpoints[0].enabled, true); assert.strictEqual(exceptionBreakpoints[1].filter, 'caught'); assert.strictEqual(exceptionBreakpoints[1].label, 'CAUGHT'); assert.strictEqual(exceptionBreakpoints[1].enabled, false); + + model.setExceptionBreakpointsForSession("session-id-3", [{ filter: 'all', label: 'ALL' }]); + assert.strictEqual(eventCount, 3); + assert.strictEqual(model.getExceptionBreakpointsForSession("session-id-3").length, 1); + exceptionBreakpoints = model.getExceptionBreakpoints(); + assert.strictEqual(exceptionBreakpoints[0].filter, 'uncaught'); + assert.strictEqual(exceptionBreakpoints[0].enabled, true); + assert.strictEqual(exceptionBreakpoints[1].filter, 'caught'); + assert.strictEqual(exceptionBreakpoints[1].label, 'CAUGHT'); + assert.strictEqual(exceptionBreakpoints[1].enabled, false); + assert.strictEqual(exceptionBreakpoints[2].filter, 'all'); + assert.strictEqual(exceptionBreakpoints[2].label, 'ALL'); + }); + + test('exception breakpoints multiple sessions', () => { + let eventCount = 0; + model.onDidChangeBreakpoints(() => eventCount++); + + model.setExceptionBreakpointsForSession("session-id-4", [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }, { filter: 'caught', label: 'CAUGHT' }]); + model.setExceptionBreakpointFallbackSession("session-id-4"); + assert.strictEqual(eventCount, 1); + let exceptionBreakpointsForSession = model.getExceptionBreakpointsForSession("session-id-4"); + assert.strictEqual(exceptionBreakpointsForSession.length, 2); + assert.strictEqual(exceptionBreakpointsForSession[0].filter, 'uncaught'); + assert.strictEqual(exceptionBreakpointsForSession[1].filter, 'caught'); + + model.setExceptionBreakpointsForSession("session-id-5", [{ filter: 'all', label: 'ALL' }, { filter: 'caught', label: 'CAUGHT' }]); + assert.strictEqual(eventCount, 2); + exceptionBreakpointsForSession = model.getExceptionBreakpointsForSession("session-id-5"); + let exceptionBreakpointsForUndefined = model.getExceptionBreakpointsForSession(undefined); + assert.strictEqual(exceptionBreakpointsForSession.length, 2); + assert.strictEqual(exceptionBreakpointsForSession[0].filter, 'caught'); + assert.strictEqual(exceptionBreakpointsForSession[1].filter, 'all'); + assert.strictEqual(exceptionBreakpointsForUndefined.length, 2); + assert.strictEqual(exceptionBreakpointsForUndefined[0].filter, 'uncaught'); + assert.strictEqual(exceptionBreakpointsForUndefined[1].filter, 'caught'); + + model.removeExceptionBreakpointsForSession("session-id-4"); + assert.strictEqual(eventCount, 2); + exceptionBreakpointsForUndefined = model.getExceptionBreakpointsForSession(undefined); + assert.strictEqual(exceptionBreakpointsForUndefined.length, 2); + assert.strictEqual(exceptionBreakpointsForUndefined[0].filter, 'uncaught'); + assert.strictEqual(exceptionBreakpointsForUndefined[1].filter, 'caught'); + + model.setExceptionBreakpointFallbackSession("session-id-5"); + assert.strictEqual(eventCount, 2); + exceptionBreakpointsForUndefined = model.getExceptionBreakpointsForSession(undefined); + assert.strictEqual(exceptionBreakpointsForUndefined.length, 2); + assert.strictEqual(exceptionBreakpointsForUndefined[0].filter, 'caught'); + assert.strictEqual(exceptionBreakpointsForUndefined[1].filter, 'all'); + + const exceptionBreakpoints = model.getExceptionBreakpoints(); + assert.strictEqual(exceptionBreakpoints.length, 3); }); test('instruction breakpoints', () => { diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index b60984f5735..fe909c8daaa 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -93,7 +93,7 @@ export class MockDebugService implements IDebugService { throw new Error('Method not implemented.'); } - setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { + setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { throw new Error('Method not implemented.'); } From ea3a260e50ff67bd08b964d0a7243d0c5db46f94 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 17 Nov 2022 13:11:42 -0800 Subject: [PATCH 63/67] kernel mru history per workspace (#166633) --- .../notebook/browser/notebook.contribution.ts | 4 +- .../notebookKernelHistoryServiceImpl.ts | 149 ++++++++++++++++ .../notebookKernelQuickPickStrategy.ts | 46 +++-- .../notebook/common/notebookKernelService.ts | 7 + .../browser/notebookKernelHistory.test.ts | 168 ++++++++++++++++++ 5 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts create mode 100644 src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index c52a7128365..1c428740fd4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -46,7 +46,7 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Event } from 'vs/base/common/event'; import { getFormattedMetadataJSON, getStreamOutputData } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { NotebookModelResolverServiceImpl } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelHistoryService, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl'; import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -105,6 +105,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { NotebookInfo } from 'vs/editor/common/languageFeatureRegistry'; import { COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/contrib/comments/browser/commentReply'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { NotebookKernelHistoryService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl'; /*--------------------------------------------------------------------------------------------- */ @@ -703,6 +704,7 @@ registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverServ registerSingleton(INotebookCellStatusBarService, NotebookCellStatusBarService, InstantiationType.Delayed); registerSingleton(INotebookEditorService, NotebookEditorWidgetService, InstantiationType.Delayed); registerSingleton(INotebookKernelService, NotebookKernelService, InstantiationType.Delayed); +registerSingleton(INotebookKernelHistoryService, NotebookKernelHistoryService, InstantiationType.Delayed); registerSingleton(INotebookExecutionService, NotebookExecutionService, InstantiationType.Delayed); registerSingleton(INotebookExecutionStateService, NotebookExecutionStateService, InstantiationType.Delayed); registerSingleton(INotebookRendererMessagingService, NotebookRendererMessagingService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts new file mode 100644 index 00000000000..916c293254f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { LinkedMap, Touch } from 'vs/base/common/map'; +import { localize } from 'vs/nls'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { INotebookKernel, INotebookKernelHistoryService, INotebookKernelService, INotebookTextModelLike } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; + +interface ISerializedKernelsListPerType { + entries: string[]; +} + +interface ISerializedKernelsList { + [viewType: string]: ISerializedKernelsListPerType; +} + +const MAX_KERNELS_IN_HISTORY = 5; + +export class NotebookKernelHistoryService extends Disposable implements INotebookKernelHistoryService { + declare _serviceBrand: undefined; + + private static STORAGE_KEY = 'notebook.kernelHistory'; + private _mostRecentKernelsMap: { [key: string]: LinkedMap } = {}; + + constructor(@IStorageService private readonly _storageService: IStorageService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService) { + super(); + + this._loadState(); + this._register(this._storageService.onWillSaveState(() => this._saveState())); + } + + getKernels(notebook: INotebookTextModelLike): { selected: INotebookKernel | undefined; all: INotebookKernel[] } { + const allAvailableKernels = this._notebookKernelService.getMatchingKernel(notebook); + const allKernels = allAvailableKernels.all; + const selectedKernel = allAvailableKernels.selected; + const suggested = (allAvailableKernels.suggestions.length === 1 ? allAvailableKernels.suggestions[0] : undefined) + ?? (allAvailableKernels.all.length === 1) ? allAvailableKernels.all[0] : undefined; + + const mostRecentKernelIds = this._mostRecentKernelsMap[notebook.viewType] ? [...this._mostRecentKernelsMap[notebook.viewType].values()] : []; + + const all = mostRecentKernelIds.map(kernelId => allKernels.find(kernel => kernel.id === kernelId)).filter(kernel => !!kernel) as INotebookKernel[]; + + return { + selected: selectedKernel ?? suggested, + all + }; + } + + addMostRecentKernel(kernel: INotebookKernel): void { + const key = kernel.id; + const viewType = kernel.viewType; + const recentKeynels = this._mostRecentKernelsMap[viewType] ?? new LinkedMap(); + + recentKeynels.set(key, key, Touch.AsOld); + + + if (recentKeynels.size > MAX_KERNELS_IN_HISTORY) { + const reserved = [...recentKeynels.entries()].slice(0, MAX_KERNELS_IN_HISTORY); + recentKeynels.fromJSON(reserved); + } + + this._mostRecentKernelsMap[viewType] = recentKeynels; + } + + private _saveState(): void { + let notEmpty = false; + for (const [_, kernels] of Object.entries(this._mostRecentKernelsMap)) { + notEmpty = notEmpty || kernels.size > 0; + } + + if (notEmpty) { + const serialized = this._serialize(); + this._storageService.store(NotebookKernelHistoryService.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } else { + this._storageService.remove(NotebookKernelHistoryService.STORAGE_KEY, StorageScope.WORKSPACE); + } + } + + private _loadState(): void { + const serialized = this._storageService.get(NotebookKernelHistoryService.STORAGE_KEY, StorageScope.WORKSPACE); + if (serialized) { + try { + this._deserialize(JSON.parse(serialized)); + } catch (e) { + this._mostRecentKernelsMap = {}; + } + } else { + this._mostRecentKernelsMap = {}; + } + } + + private _serialize(): ISerializedKernelsList { + const result: ISerializedKernelsList = Object.create(null); + + for (const [viewType, kernels] of Object.entries(this._mostRecentKernelsMap)) { + result[viewType] = { + entries: [...kernels.values()] + }; + } + return result; + } + + private _deserialize(serialized: ISerializedKernelsList): void { + this._mostRecentKernelsMap = {}; + + for (const [viewType, kernels] of Object.entries(serialized)) { + const linkedMap = new LinkedMap(); + const mapValues: [string, string][] = []; + + for (const entry of kernels.entries) { + mapValues.push([entry, entry]); + } + + linkedMap.fromJSON(mapValues); + this._mostRecentKernelsMap[viewType] = linkedMap; + } + } + + _clear(): void { + this._mostRecentKernelsMap = {}; + this._saveState(); + } +} + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.clearNotebookKernelsMRUCache', + title: { + value: localize('workbench.notebook.clearNotebookKernelsMRUCache', "Clear Notebook Kernels MRU Cache"), + original: 'Clear Notebook Kernels MRU Cache' + }, + category: Categories.Developer, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const historyService = accessor.get(INotebookKernelHistoryService) as NotebookKernelHistoryService; + historyService._clear(); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index b7940b1b9ab..f5379d218ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -23,7 +23,7 @@ import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID a import { getNotebookEditorFromEditorPane, INotebookEditor, INotebookExtensionRecommendation, JUPYTER_EXTENSION_ID, KERNEL_RECOMMENDATIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelHistoryService, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; @@ -129,7 +129,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { const notebook = editor.textModel; const scopedContextKeyService = editor.scopedContextKeyService; - const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const matchResult = this._getMatchingResult(notebook); const { selected, all } = matchResult; if (selected && controllerId && selected.id === controllerId && ExtensionIdentifier.equals(selected.extension, extensionId)) { @@ -153,7 +153,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { } if (newKernel) { - this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); + this._selecteKernel(notebook, newKernel); return true; } @@ -191,7 +191,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { extensionRecommendataionPromise?.cancel(); const currentActiveItems = quickPick.activeItems; - const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const matchResult = this._getMatchingResult(notebook); const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); quickPick.keepScrollPosition = true; @@ -244,6 +244,10 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { return false; } + protected _getMatchingResult(notebook: NotebookTextModel) { + return this._notebookKernelService.getMatchingKernel(notebook); + } + protected abstract _getKernelPickerQuickPickItems( notebookTextModel: NotebookTextModel, matchResult: INotebookKernelMatchResult, @@ -254,7 +258,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { protected async _handleQuickPick(notebook: NotebookTextModel, pick: KernelQuickPickItem, context?: KernelQuickPickContext) { if (isKernelPick(pick)) { const newKernel = pick.kernel; - this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); + this._selecteKernel(notebook, newKernel); return true; } @@ -284,6 +288,10 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { return true; } + protected _selecteKernel(notebook: NotebookTextModel, kernel: INotebookKernel) { + this._notebookKernelService.selectKernelForNotebook(kernel, notebook); + } + private async _showKernelExtension( paneCompositePartService: IPaneCompositePartService, extensionWorkbenchService: IExtensionsWorkbenchService, @@ -574,7 +582,8 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { @IPaneCompositePartService _paneCompositePartService: IPaneCompositePartService, @IExtensionsWorkbenchService _extensionWorkbenchService: IExtensionsWorkbenchService, @IExtensionService _extensionService: IExtensionService, - @ICommandService _commandService: ICommandService + @ICommandService _commandService: ICommandService, + @INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService, ) { super( @@ -590,6 +599,7 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { _commandService, ); } + protected _getKernelPickerQuickPickItems(notebookTextModel: NotebookTextModel, matchResult: INotebookKernelMatchResult, notebookKernelService: INotebookKernelService, scopedContextKeyService: IContextKeyService): QuickPickInput[] { const quickPickItems: QuickPickInput[] = []; let previousKind = ''; @@ -627,6 +637,22 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { return quickPickItems; } + protected override _selecteKernel(notebook: NotebookTextModel, kernel: INotebookKernel): void { + super._selecteKernel(notebook, kernel); + this._notebookKernelHistoryService.addMostRecentKernel(kernel); + } + + protected override _getMatchingResult(notebook: NotebookTextModel): INotebookKernelMatchResult { + const { selected, all } = this._notebookKernelHistoryService.getKernels(notebook); + const matchingResult = this._notebookKernelService.getMatchingKernel(notebook); + return { + selected: selected, + all: matchingResult.all, + suggestions: all, + hidden: [] + }; + } + protected override async _handleQuickPick(notebook: NotebookTextModel, pick: KernelQuickPickItem, context?: KernelQuickPickContext): Promise { if (pick.id === 'selectAnother') { return this.displaySelectAnotherQuickPick(notebook, context); @@ -659,10 +685,10 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { if ('command' in quickPick.selectedItems[0]) { const selectedKernelId = await this._executeCommand(notebook, quickPick.selectedItems[0].command); if (selectedKernelId) { - const { all } = await this._notebookKernelService.getMatchingKernel(notebook); + const { all } = await this._getMatchingResult(notebook); const kernel = all.find(kernel => kernel.id === `ms-toolsai.jupyter/${selectedKernelId}`); if (kernel) { - await this._notebookKernelService.selectKernelForNotebook(kernel, notebook); + await this._selecteKernel(notebook, kernel); resolve(true); } resolve(true); @@ -670,14 +696,14 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { return resolve(this.displaySelectAnotherQuickPick(notebook)); } } else if ('kernel' in quickPick.selectedItems[0]) { - await this._notebookKernelService.selectKernelForNotebook(quickPick.selectedItems[0].kernel, notebook); + await this._selecteKernel(notebook, quickPick.selectedItems[0].kernel); resolve(true); } } })); this._notebookKernelService.getKernelSourceActions2(notebook).then(actions => { quickPick.busy = false; - const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const matchResult = this._getMatchingResult(notebook); const others = matchResult.all.filter(item => item.extension.value !== JUPYTER_EXTENSION_ID); quickPickItems.push(...others.map(kernel => ({ label: kernel.label, diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 86493335977..f881f9dc789 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -139,3 +139,10 @@ export interface INotebookKernelService { getKernelSourceActions2(notebook: INotebookTextModelLike): Promise; //#endregion } + +export const INotebookKernelHistoryService = createDecorator('INotebookKernelHistoryService'); +export interface INotebookKernelHistoryService { + _serviceBrand: undefined; + getKernels(notebook: INotebookTextModelLike): { selected: INotebookKernel | undefined; all: INotebookKernel[] }; + addMostRecentKernel(kernel: INotebookKernel): void; +} diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts new file mode 100644 index 00000000000..0c0bad3cf73 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; +import { Emitter, Event } from 'vs/base/common/event'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { mock } from 'vs/base/test/common/mock'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { NotebookKernelHistoryService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl'; +import { IStorageService, IWillSaveStateEvent, StorageScope } from 'vs/platform/storage/common/storage'; + +suite('NotebookKernelHistoryService', () => { + + let instantiationService: TestInstantiationService; + let kernelService: INotebookKernelService; + let disposables: DisposableStore; + + let onDidAddNotebookDocument: Emitter; + + setup(function () { + disposables = new DisposableStore(); + + onDidAddNotebookDocument = new Emitter(); + disposables.add(onDidAddNotebookDocument); + + instantiationService = setupInstantiationService(disposables); + instantiationService.stub(INotebookService, new class extends mock() { + override onDidAddNotebookDocument = onDidAddNotebookDocument.event; + override onWillRemoveNotebookDocument = Event.None; + override getNotebookTextModels() { return []; } + }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); + kernelService = instantiationService.createInstance(NotebookKernelService); + instantiationService.set(INotebookKernelService, kernelService); + }); + + teardown(() => { + disposables.dispose(); + }); + + test('notebook kernel empty history', function () { + + const u1 = URI.parse('foo:///one'); + + const k1 = new TestNotebookKernel({ label: 'z', viewType: 'foo' }); + const k2 = new TestNotebookKernel({ label: 'a', viewType: 'foo' }); + + kernelService.registerKernel(k1); + kernelService.registerKernel(k2); + + instantiationService.stub(IStorageService, new class extends mock() { + override onWillSaveState: Event = Event.None; + override get(key: string, scope: StorageScope, fallbackValue: string): string; + override get(key: string, scope: StorageScope, fallbackValue?: string | undefined): string | undefined; + override get(key: unknown, scope: unknown, fallbackValue?: unknown): string | undefined { + if (key === 'notebook.kernelHistory') { + return JSON.stringify({ + 'foo': { + 'entries': [] + } + }); + } + + return undefined; + } + }); + + const kernelHistoryService = instantiationService.createInstance(NotebookKernelHistoryService); + + let info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + assert.equal(info.all.length, 0); + assert.ok(!info.selected); + + // update priorities for u1 notebook + kernelService.updateKernelNotebookAffinity(k2, u1, 2); + + info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + assert.equal(info.all.length, 0); + // suggested kernel should be visible + assert.deepStrictEqual(info.selected, k2); + }); + + test('notebook kernel history restore', function () { + + const u1 = URI.parse('foo:///one'); + + const k1 = new TestNotebookKernel({ label: 'z', viewType: 'foo' }); + const k2 = new TestNotebookKernel({ label: 'a', viewType: 'foo' }); + const k3 = new TestNotebookKernel({ label: 'b', viewType: 'foo' }); + + kernelService.registerKernel(k1); + kernelService.registerKernel(k2); + kernelService.registerKernel(k3); + + instantiationService.stub(IStorageService, new class extends mock() { + override onWillSaveState: Event = Event.None; + override get(key: string, scope: StorageScope, fallbackValue: string): string; + override get(key: string, scope: StorageScope, fallbackValue?: string | undefined): string | undefined; + override get(key: unknown, scope: unknown, fallbackValue?: unknown): string | undefined { + if (key === 'notebook.kernelHistory') { + return JSON.stringify({ + 'foo': { + 'entries': [ + k2.id + ] + } + }); + } + + return undefined; + } + }); + + const kernelHistoryService = instantiationService.createInstance(NotebookKernelHistoryService); + let info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + assert.equal(info.all.length, 1); + assert.deepStrictEqual(info.selected, undefined); + + kernelHistoryService.addMostRecentKernel(k3); + info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + assert.deepStrictEqual(info.all, [k3, k2]); + }); +}); + +class TestNotebookKernel implements INotebookKernel { + id: string = Math.random() + 'kernel'; + label: string = 'test-label'; + viewType = '*'; + onDidChange = Event.None; + extension: ExtensionIdentifier = new ExtensionIdentifier('test'); + localResourceRoot: URI = URI.file('/test'); + description?: string | undefined; + detail?: string | undefined; + preloadUris: URI[] = []; + preloadProvides: string[] = []; + supportedLanguages: string[] = []; + executeNotebookCellsRequest(): Promise { + throw new Error('Method not implemented.'); + } + cancelNotebookCellExecution(): Promise { + throw new Error('Method not implemented.'); + } + + constructor(opts?: { languages?: string[]; label?: string; viewType?: string }) { + this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID]; + this.label = opts?.label ?? this.label; + this.viewType = opts?.viewType ?? this.viewType; + } +} From 04ee3baf3d885188c8bf53ba0dd32f13476c2e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 17 Nov 2022 13:13:36 -0800 Subject: [PATCH 64/67] fixes #166637 (#166638) --- .../contrib/scm/browser/dirtydiffDecorator.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index b0bc4bbf0d8..5ba9b1a64ae 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -253,8 +253,8 @@ class DirtyDiffWidget extends PeekViewWidget { protected override _fillHead(container: HTMLElement): void { super._fillHead(container, true); - const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), ThemeIcon.asClassName(gotoPreviousLocation)); - const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), ThemeIcon.asClassName(gotoNextLocation)); + 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)); this._disposables.add(previous); this._disposables.add(next); @@ -363,7 +363,7 @@ class DirtyDiffWidget extends PeekViewWidget { export class ShowPreviousChangeAction extends EditorAction { - constructor() { + constructor(private readonly outerEditor?: ICodeEditor) { super({ id: 'editor.action.dirtydiff.previous', label: nls.localize('show previous change', "Show Previous Change"), @@ -373,8 +373,8 @@ export class ShowPreviousChangeAction extends EditorAction { }); } - run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const outerEditor = getOuterEditorFromDiffEditor(accessor); + run(accessor: ServicesAccessor): void { + const outerEditor = this.outerEditor ?? getOuterEditorFromDiffEditor(accessor); if (!outerEditor) { return; @@ -397,7 +397,7 @@ registerEditorAction(ShowPreviousChangeAction); export class ShowNextChangeAction extends EditorAction { - constructor() { + constructor(private readonly outerEditor?: ICodeEditor) { super({ id: 'editor.action.dirtydiff.next', label: nls.localize('show next change', "Show Next Change"), @@ -407,8 +407,8 @@ export class ShowNextChangeAction extends EditorAction { }); } - run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const outerEditor = getOuterEditorFromDiffEditor(accessor); + run(accessor: ServicesAccessor): void { + const outerEditor = this.outerEditor ?? getOuterEditorFromDiffEditor(accessor); if (!outerEditor) { return; @@ -460,7 +460,7 @@ export class GotoPreviousChangeAction extends EditorAction { }); } - run(accessor: ServicesAccessor, editor: ICodeEditor): void { + run(accessor: ServicesAccessor): void { const outerEditor = getOuterEditorFromDiffEditor(accessor); if (!outerEditor || !outerEditor.hasModel()) { @@ -502,7 +502,7 @@ export class GotoNextChangeAction extends EditorAction { }); } - run(accessor: ServicesAccessor, editor: ICodeEditor): void { + run(accessor: ServicesAccessor): void { const outerEditor = getOuterEditorFromDiffEditor(accessor); if (!outerEditor || !outerEditor.hasModel()) { From 4ff197412dc26e0daacf953ea8822fa009f38a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 17 Nov 2022 13:18:16 -0800 Subject: [PATCH 65/67] more log updates (#166640) --- src/vs/platform/policy/node/nativePolicyService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index af5957fe7df..7b78806548f 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -39,7 +39,7 @@ export class NativePolicyService extends AbstractPolicyService implements IPolic } private _onDidPolicyChange(update: PolicyUpdate>): void { - this.logService.trace(`NativePolicyService#_onDidPolicyChange - Updated policy values: ${Object.keys(update).join(', ')}`); + this.logService.trace(`NativePolicyService#_onDidPolicyChange - Updated policy values: ${JSON.stringify(update)}`); for (const key in update) { const value = update[key] as any; From 587260eb634a35e5eefcd449096362396ba96b52 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Thu, 17 Nov 2022 13:26:37 -0800 Subject: [PATCH 66/67] add comment regarding error message treat as API --- src/server-main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server-main.js b/src/server-main.js index 27ecbaed0d6..3dbbeaedf54 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -197,6 +197,7 @@ async function parsePort(host, strPort) { if (port !== undefined) { return port; } + // Remote-SSH extension relies on this exact port error message, treat as an API console.warn(`--port: Could not find free port in range: ${range.start} - ${range.end} (inclusive).`); process.exit(1); From 9feaed8f51c7ba92df573232518b0f46d8cec0b4 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 17 Nov 2022 13:53:17 -0800 Subject: [PATCH 67/67] Revert "joh/representative canidae" (#166641) --- src/bootstrap-fork.js | 8 +---- src/bootstrap-window.js | 7 ----- src/main.js | 8 ----- src/server-main.js | 7 ----- src/tsconfig.monaco.json | 1 - src/typings/require.d.ts | 9 +----- src/typings/vscode-globals-modules.d.ts | 30 ------------------- src/typings/vscode-globals-product.d.ts | 22 -------------- src/vs/base/common/performance.js | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 8 ++--- .../test/node/nativeModules.test.ts | 4 +-- src/vs/platform/product/common/product.ts | 16 ++++++---- .../node/remoteExtensionHostAgentServer.ts | 2 +- .../api/node/extHostExtensionService.ts | 2 +- .../api/node/extensionHostProcess.ts | 4 +-- src/vs/workbench/api/node/proxyResolver.ts | 2 +- test/unit/electron/renderer.js | 7 ----- test/unit/node/index.js | 9 ------ 18 files changed, 25 insertions(+), 123 deletions(-) delete mode 100644 src/typings/vscode-globals-modules.d.ts delete mode 100644 src/typings/vscode-globals-product.d.ts diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 00bf5ed7451..b9d66d444a8 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -37,16 +37,10 @@ if (process.env['VSCODE_PARENT_PID']) { terminateWhenParentTerminates(); } -// VSCODE_GLOBALS: node_modules -globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); - -// VSCODE_GLOBALS: package/product.json -globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); -globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); - // Load AMD entry point require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); + //#region Helpers function pipeLoggingToParent() { diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index e4218117b7d..8bdd75c63c0 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -112,13 +112,6 @@ window['MonacoEnvironment'] = {}; - // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => (require.__$__nodeRequire ?? require)(String(mod)) }); - - // VSCODE_GLOBALS: package/product.json - globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)(configuration.appRoot + '/product.json'); - globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)(configuration.appRoot + '/package.json'); - const loaderConfig = { baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, 'vs/nls': nlsConfig, diff --git a/src/main.js b/src/main.js index eebc049c6f5..63f8c5679f9 100644 --- a/src/main.js +++ b/src/main.js @@ -141,13 +141,6 @@ function startup(codeCachePath, nlsConfig) { process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || ''; - // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); - - // VSCODE_GLOBALS: package/product.json - globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); - globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); - // Load main in AMD perf.mark('code/willLoadMainBundle'); require('./bootstrap-amd').load('vs/code/electron-main/main', () => { @@ -325,7 +318,6 @@ function getArgvConfigPath() { dataFolderName = `${dataFolderName}-dev`; } - // @ts-ignore return path.join(os.homedir(), dataFolderName, 'argv.json'); } diff --git a/src/server-main.js b/src/server-main.js index 8eb66e83abf..27ecbaed0d6 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -258,13 +258,6 @@ function loadCode() { return new Promise((resolve, reject) => { const path = require('path'); - // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); - - // VSCODE_GLOBALS: package/product.json - globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); - globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); - delete process.env['ELECTRON_RUN_AS_NODE']; // Keep bootstrap-amd.js from redefining 'fs'. // See https://github.com/microsoft/vscode-remote-release/issues/6543 diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 256f406fb6c..057aa8748ae 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -18,7 +18,6 @@ "include": [ "typings/require.d.ts", "typings/thenable.d.ts", - "typings/vscode-globals-product.d.ts", "vs/loader.d.ts", "vs/monaco.d.ts", "vs/editor/*", diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts index 3fda6d6981d..671692c88da 100644 --- a/src/typings/require.d.ts +++ b/src/typings/require.d.ts @@ -45,17 +45,10 @@ interface NodeRequire { * @deprecated use `FileAccess.asFileUri()` for node.js contexts or `FileAccess.asBrowserUri` for browser contexts. */ toUrl(path: string): string; - - /** - * @deprecated MUST not be used anymore - * - * With the move from AMD to ESM we cannot use this anymore. There will be NO MORE node require like this. - */ - __$__nodeRequire(moduleName: string): T; - (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; config(data: any): any; onError: Function; + __$__nodeRequire(moduleName: string): T; getStats?(): ReadonlyArray; hasDependencyCycle?(): boolean; define(amdModuleId: string, dependencies: string[], callback: (...args: any[]) => any): any; diff --git a/src/typings/vscode-globals-modules.d.ts b/src/typings/vscode-globals-modules.d.ts deleted file mode 100644 index 443c2b687db..00000000000 --- a/src/typings/vscode-globals-modules.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// AMD2ESM mirgation relevant - -declare global { - - /** - * @deprecated node modules that are in used in a context that - * shouldn't have access to node_modules (node-free renderer or - * shared process) - */ - var _VSCODE_NODE_MODULES: { - crypto: typeof import('crypto'); - zlib: typeof import('zlib'); - net: typeof import('net'); - os: typeof import('os'); - module: typeof import('module'); - ['native-watchdog']: typeof import('native-watchdog') - perf_hooks: typeof import('perf_hooks'); - - ['vsda']: any - ['vscode-encrypt']: any - } -} - -// fake export to make global work -export { } diff --git a/src/typings/vscode-globals-product.d.ts b/src/typings/vscode-globals-product.d.ts deleted file mode 100644 index 780a6477de8..00000000000 --- a/src/typings/vscode-globals-product.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// AMD2ESM mirgation relevant - -declare global { - - /** - * @deprecated You MUST use `IProductService` whenever possible. - */ - var _VSCODE_PRODUCT_JSON: Record; - /** - * @deprecated You MUST use `IProductService` whenever possible. - */ - var _VSCODE_PACKAGE_JSON: Record; - -} - -// fake export to make global work -export { } diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index 92e261d898a..15eab308bf2 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -78,7 +78,7 @@ } else if (typeof process === 'object') { // node.js: use the normal polyfill but add the timeOrigin // from the node perf_hooks API as very first mark - const timeOrigin = Math.round((require.__$__nodeRequire || require)('perf_hooks').performance.timeOrigin); + const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin); return _definePolyfillMarks(timeOrigin); } else { diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 455caa3efeb..810ba41d106 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -20,10 +20,10 @@ import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEv // TODO@bpasero remove me once electron utility process has landed function getNodeDependencies() { return { - crypto: globalThis._VSCODE_NODE_MODULES.crypto, - zlib: globalThis._VSCODE_NODE_MODULES.zlib, - net: globalThis._VSCODE_NODE_MODULES.net, - os: globalThis._VSCODE_NODE_MODULES.os, + crypto: (require.__$__nodeRequire('crypto') as any) as typeof import('crypto'), + zlib: (require.__$__nodeRequire('zlib') as any) as typeof import('zlib'), + net: (require.__$__nodeRequire('net') as any) as typeof import('net'), + os: (require.__$__nodeRequire('os') as any) as typeof import('os') }; } diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts index c538d8c3220..6b7dfad6741 100644 --- a/src/vs/platform/environment/test/node/nativeModules.test.ts +++ b/src/vs/platform/environment/test/node/nativeModules.test.ts @@ -58,7 +58,7 @@ flakySuite('Native Modules (all platforms)', () => { test('vscode-encrypt', async () => { try { - const vscodeEncrypt: Encryption = globalThis._VSCODE_NODE_MODULES['vscode-encrypt']; + const vscodeEncrypt: Encryption = require.__$__nodeRequire('vscode-encrypt'); const encrypted = await vscodeEncrypt.encrypt('salt', 'value'); const decrypted = await vscodeEncrypt.decrypt('salt', encrypted); @@ -73,7 +73,7 @@ flakySuite('Native Modules (all platforms)', () => { test('vsda', async () => { try { - const vsda: any = globalThis._VSCODE_NODE_MODULES['vsda']; + const vsda: any = require.__$__nodeRequire('vsda'); const signer = new vsda.signer(); const signed = await signer.sign('value'); assert.ok(typeof signed === 'string', testErrorMessage('vsda')); diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index ef798fa4699..3f50bef5ca2 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { FileAccess } from 'vs/base/common/network'; import { globals } from 'vs/base/common/platform'; import { env } from 'vs/base/common/process'; import { IProductConfiguration } from 'vs/base/common/product'; +import { dirname, joinPath } from 'vs/base/common/resources'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; /** @@ -22,10 +24,14 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.context !== ' throw new Error('Sandbox: unable to resolve product configuration from preload script.'); } } -// _VSCODE environment -else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { - // Obtain values from product.json and package.json-data - product = globalThis._VSCODE_PRODUCT_JSON as IProductConfiguration; + +// Native node.js environment +else if (typeof require?.__$__nodeRequire === 'function') { + + // Obtain values from product.json and package.json + const rootPath = dirname(FileAccess.asFileUri('')); + + product = require.__$__nodeRequire(joinPath(rootPath, 'product.json').fsPath); // Running out of sources if (env['VSCODE_DEV']) { @@ -41,7 +47,7 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { // want to have it running out of sources so we // read it from package.json only when we need it. if (!product.version) { - const pkg = globalThis._VSCODE_PACKAGE_JSON as { version: string }; + const pkg = require.__$__nodeRequire(joinPath(rootPath, 'package.json').fsPath) as { version: string }; Object.assign(product, { version: pkg.version diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index ce679e09201..efc591aab18 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -727,7 +727,7 @@ export async function createServer(address: string | net.AddressInfo | null, arg const hasVSDA = fs.existsSync(join(FileAccess.asFileUri('').fsPath, '../node_modules/vsda')); if (hasVSDA) { try { - return globalThis._VSCODE_NODE_MODULES['vsda']; + return require.__$__nodeRequire('vsda'); } catch (err) { logService.error(err); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 06f91272771..32e4f223aad 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -22,7 +22,7 @@ class NodeModuleRequireInterceptor extends RequireInterceptor { protected _installInterceptor(): void { const that = this; - const node_module = globalThis._VSCODE_NODE_MODULES.module; + const node_module = require.__$__nodeRequire('module'); const originalLoad = node_module._load; node_module._load = function load(request: string, parent: { filename: string }, isMain: boolean) { request = applyAlternatives(request); diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 07289755555..5b0030167cd 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -81,7 +81,7 @@ const args = minimist(process.argv.slice(2), { // happening we essentially blocklist this module from getting loaded in any // extension by patching the node require() function. (function () { - const Module = globalThis._VSCODE_NODE_MODULES.module as any; + const Module = require.__$__nodeRequire('module') as any; const originalLoad = Module._load; Module._load = function (request: string) { @@ -325,7 +325,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise): Promise { return extensionService.getExtensionPathIndex() .then(extensionPaths => { - const node_module = globalThis._VSCODE_NODE_MODULES.module; + const node_module = require.__$__nodeRequire('module'); const original = node_module._load; node_module._load = function load(request: string, parent: { filename: string }, isMain: boolean) { if (request === 'tls') { diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index a91725887a5..47f8fb0a53e 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -72,13 +72,6 @@ if (util.inspect && util.inspect['defaultOptions']) { util.inspect['defaultOptions'].customInspect = false; } -// VSCODE_GLOBALS: node_modules -globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => (require.__$__nodeRequire ?? require)(String(mod)) }); - -// VSCODE_GLOBALS: package/product.json -globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)('../../../product.json'); -globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)('../../../package.json'); - const _tests_glob = '**/test/**/*.test.js'; let loader; let _out; diff --git a/test/unit/node/index.js b/test/unit/node/index.js index c6474162dfc..1fb4fd57020 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -56,15 +56,6 @@ if (majorRequiredNodeVersion !== currentMajorNodeVersion) { } function main() { - - // VSCODE_GLOBALS: node_modules - globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); - - // VSCODE_GLOBALS: package/product.json - globalThis._VSCODE_PRODUCT_JSON = require(`${REPO_ROOT}/product.json`); - globalThis._VSCODE_PACKAGE_JSON = require(`${REPO_ROOT}/package.json`); - - process.on('uncaughtException', function (e) { console.error(e.stack || e); });